SKView CAMetalLayer framebufferOnly = false flips content by y. Why? - sprite-kit

This code cause SKView flip content by Y:
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
...
(sceneView.layer as? CAMetalLayer)?.framebufferOnly = false
View playground with this bug on GitHub.com
I finded solve, but don't know how good is
sceneView.transform = CGAffineTransform.identity.scaledBy(x: 1.0, y: -1.0)
But still don't understand why this flip happens, and if there exist better solved of this?

I finded solve, but don't know how good is
sceneView.transform = CGAffineTransform.identity.scaledBy(x: 1.0, y: -1.0)
But still don't understand why this flip happens, and if there exist better solved of this?

Related

animation not moving upwards in chaining animation in swift

I want my swift code to follow the gif I created. What is going is after the first animation there is not any change of the position. Like the gif I created it should first move down then move up again. I tried multiplying by the negative -0.15 thinking it would go in the north direction. Right now it is not having a effect.
UIView.animate(withDuration: 1, animations: {
self.block1.frame = CGRect(x: 0, y: self.view.frame.height * 0.15, width: self.view.frame.width, height: self.view.frame.height * 0.1)
self.block1.center = self.view.center
}) { done in
/// first animation finished, start second
if done {
UIView.animate(withDuration: 1, animations: {
self.block1.frame = CGRect(x: 0, y: self.view.frame.height * (-0.15), width: self.view.frame.width, height: self.view.frame.height * 0.1)
self.block1.center = self.view.center
})
}
}
Start by getting rid of self.block1.center = self.view.center as you're making the centre point of the block the same as the view in both cycles.
self.view.frame.height * (-0.15) is setting an absolute position out side of the view, not sure if that's intentional, but you might want to be aware of it. Instead, if you only wanted to move a given distance, say the height of the view, you should be using a relative position, such as block1.frame.origin.y -= block.frame.size.height (probably a much easier way to say that, but you get the idea)
So I created a really quick Playground, made up some values and got a "sort of" bouncy, returny thing going as an example
import UIKit
import XCPlayground
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0))
// XCPShowView("container", view: container) // -> deprecated
let block1 = UIView(frame: CGRect(x: 0.0, y: 0.0, width: (view.frame.width) / 2, height: 150))
block1.backgroundColor = UIColor.green
block1.center.x = view.center.x
view.addSubview(block1)
UIView.animate(withDuration: 1, animations: {
let y = view.frame.height * 0.15
print(y)
block1.center.y = view.center.y
}) { done in
print("Done = \(done)")
/// first animation finished, start second
if done {
UIView.animate(withDuration: 1, animations: {
block1.frame.origin.y = 0
})
}
}
XCPlaygroundPage.currentPage.liveView = view
Caveat: There's probably a really neat and easy way to do "auto reversal" style animations using CALayer and I'd be keen to see other examples presenting the same concept

ARKit: Occlude everything in a plane except reference image

I have an image reference that is triggering an experience on ARKit2, and I want to create an occlusion plane that covers everything around my trigger, except the trigger itself. Representatively, it is something like this (placed the image below the plane to show better, but it is in reality on the same level):
As of now, I have no issue with making an occlusion on a regular shape like so:
func setOcclusionMaterial() {
let occlusionPlane = SCNPlane(width: imageReference!.physicalSize.width, height: imageReference!.physicalSize.height)
let holdout = SCNMaterial()
holdout.isDoubleSided = true
holdout.diffuse.contents = CIColor.black
holdout.colorBufferWriteMask = SCNColorMask(rawValue: 0)
occlusionPlane.firstMaterial? = holdout
let occlusionNode = SCNNode()
occlusionNode.eulerAngles.x = -.pi / 2
occlusionNode.geometry = occlusionPlane
node!.addChildNode(occlusionNode)
}
However, I'm not sure how I could create a plane that excludes the image area (a circle).
What I'm trying to do is to place a 3D element that appears to be below my trigger but only visible if seen top-down or with a slight angle.
Hope this is clear enough and thanks for your help if you have any ideas.
Your code does not render an occlusion for me, at least not in the Playground I quickly threw together. Maybe you can create one if my answer does not actually solve your problem.
You have to options here. Either create a custom geometry, in a 3D modelling tool like blender or manual via SCNGeometrySource but that is expansive because you will need a lot of triangles to make it work.
You can try to apply a CALayer as the plane's geometry:
Create a CGPath that is the rectangle with the circular cutout (or any form you'd like) and create a CAShapeLayer from the path.
var path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
path.addEllipse(in: CGRect(x: 25, y: 25, width: 50, height: 50))
let layer = CAShapeLayer()
layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.path = path
layer.fillColor = UIColor.white.cgColor
layer.fillRule = .evenOdd
I could not confirm this actually worked because your occlusion is not quite working for me.

Filling animation + permanent mask in swift?

Something like this:
Or follow this link if you can:
https://www.lottiefiles.com/450-play-fill-loader
My case is simpler - I have a view with mask which consists of multiple rects and a linear fill. Mask is created in runtime (so I can't use lottie) but remains permanent, fill is animated (filling from right to left). But how to draw it?
Note: I tried to find a similar animation implementation but in most cases they just try to change the mask params while in my case mask is constant.
If i understand you correct, you can add subview under your mask and change its width, something like that:
let fillingStartFrame = CGRect.init(x: 0, y: 0, width: 0, height: view.frame.height)
let fillingEndFrame = CGRect.init(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
let fillingView = UIView(frame: fillingStartFrame)
view.insertSubview(fillingView, belowSubview: YOUR_MASK)
UIView.animate(withDuration: 0.3) {
fillingView.frame = fillingEndFrame
}

SpriteKit: unwanted stretching of sprite when resizing/scaling

I've seen similar questions before but seems that no clear solution is provided. Currently I have a SKScene with child, contained by a SKView. Note: I'm not using a Game project; instead, I'm using the SKView in a normal UIView, and just add SKView as the subview of the UIView. The sprite itself is a small image, and it only occupies a certain portion in the middle-bottom part of the whole frame. The code looks something like this:
let imageView = SKView()
imageView.frame = CGRect(x: ..., y: ..., width: ..., height: ...) //all are predefined constants
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "image"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5) // so it's positioned in the center
let scene = SKScene(size: imageView.frame.size) // also tried with sprite.size, but not working
scene.addChild(sprite)
scene.scaleMode = .aspectFit
imageView.presentScene(scene)
self.frame.addSubview(imageView)
Now the problem is the sprite is not in its original shape; it's stretched along the vertical axis. I tried changing the scaleMode with all 5 choices: .fill / .aspectFill / .aspectFit / .resizeFill / none, but none of them give the sprite its original shape/ratio. I've also tried changing the constants for the imageView's frame, but that only cuts the sprite (if imageView.frame.height is decreased). So may I know how to address this issue?
Your code works fine as written, so the problem is likely elsewhere. I modified it a little bit so you can test it out in a Playground. Just make a new Playground and copy and paste. You'll also have to drag in an image for your sprite.
import SpriteKit
import PlaygroundSupport
// create the outer UIView and show it on the playground
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundPage.current.liveView = outerView
// create the SKView and add it to the outer view
let imageView = SKView(frame: CGRect(x: 25, y: 25, width: 250, height: 250))
outerView.addSubview(imageView)
// create the scene and present it
var scene = SKScene(size: imageView.frame.size)
imageView.presentScene(scene)
// create the sprite, position it, add it to the scene
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "worf.jpg"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5)
scene.scaleMode = .aspectFit
scene.addChild(sprite)
Here's the result:
No stretching! So your issue is probably related to the UIView you're placing it in. For example if you add:
outerView.transform = CGAffineTransform(scaleX: 1, y: 2)
Now the image is stretched as you describe. Take a look at your containing view and everything above it.

How to contain SKEmitterNode particles in parent Node?

I would like to add a SKEmitterNode to SKNode but have its particles stay inside the parent node's frame. Kinda like clipsToBounds property on UIView.
Example: the particles from the emitter should not leave the black square SKSpriteNode:
You could do it with SKCropNode. Like this:
if let particles = SKEmitterNode(fileNamed: "rain.sks") {
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
cropNode.zPosition = 3
cropNode.maskNode = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 150, height: 150))
cropNode.addChild(particles)
addChild(cropNode)
}
Unfortunately this works only on iOS8... When you try to add an emitter into crop node in iOS9, you will probably run into some issues, eg. nothing will be rendered and fps drop may occur. And this is already known issue.
Like it's said in that link, instead of particles being rendered, nothing actually happens. Personally, I haven't experienced fps issues , but particles are definitely not rendered.
A workaround would be to add a node which will hold an emitter, and then mask that container node. So, let's add an SKSpriteNode to make that black background like from your example. Let's call it background:
if let particles = SKEmitterNode(fileNamed: "rain.sks") {
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
cropNode.zPosition = 3
let blackNode = SKSpriteNode(color: .blackColor(), size: CGSize(width: 200, height: 200))
blackNode.addChild(particles)
cropNode.maskNode = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 200, height: 200))
cropNode.addChild(blackNode)
addChild(cropNode)
}