Programmatically create constrained region of physics, SpriteKit - ios

I would like two regions, as shown in the image below, where the yellow region is to contain sprites. For example I'd like to have balls in the yellow region bouncing and reflecting off the boundaries of the yellow region. How can I programmatically do this without using an sks file?

You create an edge based physics body using the +bodyWithEdgeLoopFromRect:
override func didMoveToView(view: SKView) {
//Setup scene's physics body (setup the walls)
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
let yellowSprite = SKSpriteNode(color: .yellowColor(), size: CGSize(width: 300, height: 300))
yellowSprite.position = CGPoint(x: frame.midX, y: frame.midY)
//Create the rectangle which will represent physics body.
let rect = CGRect(origin: CGPoint(x: -yellowSprite.size.width/2, y: -yellowSprite.size.height/2), size: yellowSprite.size)
yellowSprite.physicsBody = SKPhysicsBody(edgeLoopFromRect: rect)
addChild(yellowSprite)
//Add Red ball "inside" the yellow sprite
let red = SKShapeNode(circleOfRadius: 20)
red.fillColor = .redColor()
red.strokeColor = .clearColor()
red.position = yellowSprite.position
red.physicsBody = SKPhysicsBody(circleOfRadius: 20)
red.physicsBody?.restitution = 1
red.physicsBody?.friction = 0
red.physicsBody?.affectedByGravity = false
addChild(red)
red.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 15))
}
About rect parameter:
The rectangle that defines the edges. The rectangle is specified
relative to the owning node’s origin.
Hope this helps!

Related

SKSpriteNode shadow rendering incorrectly?

I'm using Xcode 11.4.1 and Swift 5.2.2, and I am trying cast a shadow using an SKLightNode onto a circular SKSpriteNode.
However, the shadow is cast over the rectangular frame of the SpriteNode rather than the circle image png that I am using.
This is the desired effect
This is what happens
In the first image, I am using a 611x611px circle png while in the second I am using a 612x612 png. I have found that this "corner shadow" happens only when using specific image dimensions to create the SpriteNode.
Specifically, through my testing, square images with size <688px and >601px display no corner shadow, except sizes exactly 612 and 608. I am testing this in a playground but the same problem occurs in a full xcodeproj.
What have I done wrong here? I doubt my code is the issue but here it is:
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let background = SKSpriteNode(color: UIColor.blue, size: frame.size)
background.zPosition = 0
background.position = CGPoint(x: frame.midX, y: frame.midY)
background.lightingBitMask = 1
addChild(background)
let light = SKLightNode()
light.zPosition = 100
light.categoryBitMask = 1
light.falloff = 0.5
light.lightColor = UIColor.white
light.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(light)
let sprite = SKSpriteNode(imageNamed: "612.png")
sprite.color = UIColor.yellow
sprite.colorBlendFactor = 1
sprite.zPosition = 3
sprite.position = CGPoint(x: frame.midX, y: frame.midY + 100)
sprite.size = CGSize(width: 100, height: 100)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 50)
sprite.physicsBody?.categoryBitMask = 2
sprite.shadowCastBitMask = 1
sprite.lightingBitMask = 1
sprite.physicsBody?.isDynamic = false
addChild(sprite)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
let scene = GameScene()
scene.scaleMode = .aspectFill
scene.size = sceneView.frame.size
sceneView.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView

Endless Runner reposition player for equal difficulty regardless of aspect ratio

I am creating and endless runner for iphones in landscape mode using SpriteKit. I set up the scene as such:
override func viewDidLayoutSubviews() {
let scene = GameScene(size: CGSize(width: 812, height: 375))
let skView = view as! SKView
scene.backgroundColor = UIColor.red
scene.scaleMode = .aspectFill
skView.presentScene(scene)
skView.showsFPS = true
skView.showsNodeCount = true
print("Screen Size: \(GlobalProperties.screenSize.width) x \(GlobalProperties.screenSize.height)")
print("Scene Size: \(scene.size.width) x \(scene.size.height)")
}
I would like to position the player so that there is the same amount of pixels between the edge of the player and the right edge of the screen regardless of aspect ratio. Is this a reasonable practice for maintaining difficulty between devices? I have the layout setup as such:
override func didMove(to view: SKView) {
let player = SKSpriteNode(texture: SKTexture(imageNamed: "CuteMelon"))
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: frame.width - 600, y: frame.midY)
self.addChild(player)
let rect = SKSpriteNode(color: .orange, size: CGSize(width: 550, height: 200))
rect.anchorPoint = CGPoint(x: 1, y: 0.5)
rect.position = CGPoint(x: frame.width-50, y: frame.height/2)
self.addChild(rect)
}
I added the rectangle to see if in both cases the player was 600 pixels from the right (leaving a 50px gap to ensure it wasnt running off the edge)
The result is as follows:
iPhone XR: https://imgur.com/y6OYHkK which is working as intended
iPhone 8: https://imgur.com/nkrx5By which is not placing the rectangle 50 pixels from the right bound of the frame.
What do I have to do to fix this issue or should I go about solving it a different way entirely? Thank you
Simply figure out the difference between the scene size and the screen size, and shift the camera over by half that distance. The formula abs((sceneWidth - screenWidth * sceneHeight/screenHeight)/2) will get you that. What this does is scale the screen to whatever height the scene is, then subtract the two differences from the width and return half that value.
override func didMove(to view: SKView) {
let widthPadding = abs((self.frame.width - (UIScreen.main.bounds.width * self.frame.height / UIScreen.main.bounds.height )) / 2)
self.camera = self.camera ?? SKCameraNode()
self.camera.position.x += widthPadding
let player = SKSpriteNode(texture: SKTexture(imageNamed: "CuteMelon"))
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: frame.width - 600, y: frame.midY)
self.addChild(player)
let rect = SKSpriteNode(color: .orange, size: CGSize(width: 550, height: 200))
rect.anchorPoint = CGPoint(x: 1, y: 0.5)
rect.position = CGPoint(x: frame.width-50, y: frame.height/2)
self.addChild(rect)
}

SpriteKit - SKLightNode shadow blend mode

I have 3 SKLightNodes each one with a light color: Red, Green and Blue. The effect that I want is the shadow generated by the SKLightNodes to have a blend mode.
Xcode Simulator
I did some photoshop examples.
This is the shadow current behavior:
This is the shadow behavior desired:
Is this possible to do in SpriteKit?
Color space
Hi, first of all, please note that there are different Color Spaces.
The result of mixing 2 colors depends on the Color Space you want to use.
RGB Color Space
With this space color you can represent the intersection of the 2 raylights.
In this case, when 2 colors are mixed, the intersection is brighter.
You can get this effect in SpriteKit using blendMode = .add.
Here's the full code.
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let red = SKShapeNode(circleOfRadius: 100)
red.fillColor = .red
red.blendMode = .add
red.position = CGPoint(x: 0, y: 0)
addChild(red)
let green = SKShapeNode(circleOfRadius: 100)
green.fillColor = .green
green.blendMode = .add
green.position = CGPoint(x: 0, y: 100)
addChild(green)
let blue = SKShapeNode(circleOfRadius: 100)
blue.fillColor = .blue
blue.blendMode = .add
blue.position = CGPoint(x: 87, y: 50)
addChild(blue)
}
}
And the result
CMY Colorspace
This color space represents the real world scenario of mixing 2 fluids.
Now mixing to colors produce a new darker color.
You can get this effect in SpriteKit simply using blendMode = .multiply (and IO suggest a white background).
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.backgroundColor = .white
let yellow = SKShapeNode(circleOfRadius: 100)
yellow.fillColor = .yellow
yellow.blendMode = .multiply
yellow.position = CGPoint(x: 0, y: 0)
addChild(yellow)
let cyan = SKShapeNode(circleOfRadius: 100)
cyan.fillColor = .cyan
cyan.blendMode = .multiply
cyan.position = CGPoint(x: 0, y: 100)
addChild(cyan)
let magenta = SKShapeNode(circleOfRadius: 100)
magenta.fillColor = .magenta
magenta.blendMode = .multiply
magenta.position = CGPoint(x: 87, y: 50)
addChild(magenta)
}
}

SKCropNode fails when I add extra SKNode children in hierarchy

Update: It looks like iOS 10 has fixed this issue. I upgraded to Swift 3 and Xcode 8 and everything is working as expected.
I've run into this issue a couple times now and I can't tell if it's a bug in SKCropNode or if I'm just misusing it. Perhaps there's some bit of documentation I'm missing to explain why this is happening?
I have a crop node with a 100x100 rectangle shape as the mask. If I place a blue circle inside it, it gets cropped properly.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a blue circle and put it in the crop node.
let blueCircle = SKShapeNode(circleOfRadius: 110)
blueCircle.fillColor = UIColor.blueColor()
blueCircle.strokeColor = UIColor.clearColor()
cropNode.addChild(blueCircle)
Now, when I place that same circle inside of an otherwise empty SKNode and place that container inside the same crop node, cropping fails.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a container to hold the circle.
let container = SKNode()
cropNode.addChild(container)
// Create a blue circle and put it in the container.
let blueCircle = SKShapeNode(circleOfRadius: 110)
blueCircle.fillColor = UIColor.blueColor()
blueCircle.strokeColor = UIColor.clearColor()
container.addChild(blueCircle)
But a sprite in that same container seems to be cropped fine.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a container to hold the sprite.
let container = SKNode()
cropNode.addChild(container)
// Create a spaceship and add it to the container.
let spaceshipNode = SKSpriteNode(imageNamed: "Spaceship")
spaceshipNode.anchorPoint = CGPointZero
container.addChild(spaceshipNode)
SKShapeNode is bugged, best to avoid it at all costs. Use it to create your shapes, then convert it to a texture for use with SKSpriteNode

Is it possible to position physicsbody to only one part of the Sprite?

Is it possible to position a physics body on a sprite? I only want a certain part of my sprite node to have collision detection, not the whole image.
Heres my physics body
physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: CGFloat(54.0), height: CGFloat(100.0)))
But i want to position the physics body at the top of the node, where it usually gets placed in the middle of the node.
You can try creating a smaller SKSpriteNode of the same size as the SKPhysicsBody and adding the larger SKSpriteNode as a child to the smaller one. Changing the position of the larger one as you want. For example
override func didMoveToView(view: SKView) {
let smallerSprite = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(30, 30))
smallerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: smallerSprite.size)
smallerSprite.position = CGPointMake(100, 400)
self.addChild(smallerSprite)
let largerSprite = SKSpriteNode(color: UIColor(white: 0.5, alpha: 0.5), size: CGSizeMake(100, 100))
largerSprite.position = CGPointMake(-10, -10)
smallerSprite.addChild(largerSprite)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
As an addition to rakesh's answer...A different approach to get the same result would be to use
+ bodyWithRectangleOfSize:center: method. Like this:
override func didMoveToView(view: SKView) {
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSize(width: 100.0, height: 100.0))
//I assume that you have initialized view and scene properly. If so, this will position a sprite in the middle of the screen.
sprite.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
var physicsBodySize:CGSize = CGSize(width: sprite.size.width, height: 30.0) //Create a size here. You can play with height parameter.
sprite.physicsBody =
SKPhysicsBody(rectangleOfSize: physicsBodySize, center: CGPoint(x: 0.0, y: sprite.size.height / 2.0 - physicsBodySize.height / 2.0))
//Not needed, just set to restrict that sprite can't off screen
sprite.physicsBody?.affectedByGravity = false
self .addChild(sprite)
}
The result:
If you try to change the height of physics body to 10.0, you will get something like this:

Resources