SKTextureAtlas gets overridden by declaration of second SKTextureAtlas - swift

Whenever I create and define two variables as type SKTextureAtlas they rewrite each other. Here is my initial creation of my two SKTextureAtlas
class GameScene: SKScene {
var idle = true
var TextureAtlas = SKTextureAtlas()
var TextureAtlasIdle = SKTextureAtlas()
Later on in my code I assign a folder of images to each atlas. If I comment out the second atlas my animation works, but when I define the two as shown below my animation overlaps and plays frames from cat_walk even though it is told to only play cat_idle
TextureAtlasIdle = SKTextureAtlas(named: "cat_idle")
for i in 1...TextureAtlasIdle.textureNames.count{
let Name = "\(i).png"
TextureArrayIdle.append(SKTexture (imageNamed: Name))
}
TextureAtlas = SKTextureAtlas(named: "cat_walk")
This is how I start my cat_idle animation. I don't start the cat_walk animation
aN.run(SKAction.repeatForever(SKAction.animate(with:self.TextureArrayIdle, timePerFrame: 0.1)))
I'm trying to find out why this line of code is causing my two different animations to overlap.
TextureAtlas = SKTextureAtlas(named: "cat_walk")

Texture Atlas is kind of a sprite sheet for your game. Your approach is not right for accessing atlas images for the game. modify your for loop like below:
for i in 0..< 8 {
let texture:SKTexture = TextureAtlasIdle.textureNamed(String(format: "%i", i+1))
TextureArrayIdle.insert(texture, at:i)
}
Every time you access your atlas folder, use this for loop procedure. i have used static 8 number in for loop, it would be your sprites count. There is a simple game called Desert Run in github. please check this out for more clarification.
NB: your cat's images naming must start with 1.png
if you want to run the for loop

Related

PhysicsBody: Could not create physics body

I have an entire working game that has been up on the App Store, but since iOS 13 it simply does not work. I've installed the game onto my device via Xcode and I'm getting a lot of errors coming up saying:
PhysicsBody: Could not create physics body.
I've been creating my SKSpriteNodes like this:
let bird = SKSpriteNode(texture: SKTextureAtlas(named:"player").textureNamed("bird0001"))
bird.physicsBody = SKPhysicsBody(texture: bird.texture!,
size: bird.size)
Based on some research, this is possibly an ongoing bug with iOS and Xcode. Could someone please confirm if this is the case, as this seems to be like a major problem for Games on the app store that create their SKSpriteNodes using textures?
Is there a fix to this where textures are needed?
OK, here's a test of different approaches for avoiding this bug as of iOS 13.3 (edit also now tried on 13.3.1) and Xcode version 11.3. Full source of the test at this link:
https://github.com/bg2b/bugtest
Relevant code:
func addShip(_ texture: SKTexture, how: String) {
let sprite = SKSpriteNode(texture: texture)
sprite.position = CGPoint(x: x, y: 0)
sprite.zRotation = .pi / 4
x += 100
sprite.physicsBody = SKPhysicsBody(texture: texture, size: sprite.size)
if sprite.physicsBody == nil {
print("\(how) failed")
} else {
print("\(how) worked")
}
addChild(sprite)
}
override func didMove(to view: SKView) {
// The atlas version of a texture
addShip(SKTexture(imageNamed: "ship_blue"), how: "simple atlas reference")
// From an atlas, but call size() to force loading
let texture = SKTexture(imageNamed: "ship_blue")
_ = texture.size()
addShip(texture, how: "atlas force load")
// Reconstruct via CGImage (size would be wrong because of 2x)
let cgTexture = SKTexture(cgImage: texture.cgImage())
addShip(cgTexture, how: "reconstruct via cgImage")
// Re-render using view
let renderedTexture = view.texture(from: SKSpriteNode(texture: texture))!
addShip(renderedTexture, how: "re-render using view")
// Non-atlas texture
addShip(SKTexture(imageNamed: "nonatlas_ship_blue"), how: "not in atlas")
}
Summary:
Simply referencing the texture from an atlas and making the physics body may fail
Force-loading the texture by calling size() before making the body fails
Trying to make a new texture by going through cgImage() fails (the image itself is broken, probably related to the same bug)
Rendering to a texture using a view and then making the physics body from that new texture works
Making the physics body from a non-atlas copy of the texture works
Console output from the test program showing what works and what does not:
2020-02-01 06:23:51.872975-0500 bugtest[14399:9898087] PhysicsBody: Could not create physics body.
simple atlas reference failed
2020-02-01 06:23:51.886387-0500 bugtest[14399:9898087] PhysicsBody: Could not create physics body.
atlas force load failed
2020-02-01 06:23:51.913927-0500 bugtest[14399:9898087] PhysicsBody: Could not create physics body.
reconstruct via cgImage failed
re-render using view worked
not in atlas worked
Here's a screen shot showing the effect of the different approaches. You have to look a bit closely, but only the last two have valid physics bodies.

ARKit Adding node causes frame drop even after using `prepare`

I am adding a 3D model containing animations to the scene that I previously download from the internet. Before adding this node I use prepare function on it because I wan't to avoid frame drop. But still I get a very short frame drop to about 47 fps. This is caused by executing this prepare function. I also tried using prepare(_:, shouldAbortBlock:) on other dispatch queue, but this still didn't help. Can someone help me resolve this or tell me why there is this happening?
arView.sceneView.prepare([mediaNode]) { [mediaNode, weak self] (success) in
guard let `self` = self else { return }
guard
let currentMediaNode = self.mediaNode as? SCNNode,
currentMediaNode === mediaNode,
!self.mainNode.childNodes.contains(mediaNode)
else { return }
self.mainNode.addChildNode(mediaNode)
}
By the way this is a list of files I'm using to load this model:
https://www.dropbox.com/s/7968fe5wfdcxbyu/Serah-iOS.dae?dl=1
https://www.dropbox.com/s/zqb6b6rxynnvc5e/0001.png?dl=1
https://www.dropbox.com/s/hy9y8qyazkcnvef/0002.tga?dl=1
https://www.dropbox.com/s/fll9jbjud7zjlsq/0004.tga?dl=1
https://www.dropbox.com/s/4niq12mezlvi5oz/0005.png?dl=1
https://www.dropbox.com/s/wikqgd46643327i/0007.png?dl=1
https://www.dropbox.com/s/fioj9bqt90vq70c/0008.tga?dl=1
https://www.dropbox.com/s/4a5jtmccyx413j7/0010.png?dl=1
DAE file is already compiled by Xcode tools so that it can be loaded after being downloaded from the internet. And this is the code I use to load it after it's downloaded:
class func loadModel(fromURL url: URL) -> SCNNode? {
let options = [SCNSceneSource.LoadingOption.animationImportPolicy : SCNSceneSource.AnimationImportPolicy.playRepeatedly]
let sceneSource = SCNSceneSource(url: url, options: options)
let node = sceneSource?.entryWithIdentifier("MDL_Obj", withClass: SCNNode.self)
return node
}
I was experiencing the same issue. My nodes were all taking advantage of physically-based rendering (PBR) and the first time I added a node to the scene, the frame rate dropped significantly, but was fine after that. I could add as many other nodes without a frame rate drop.
I figured out a work around to this issue. What I do is after I create my ARConfiguration and before I call session.run(configuration) I add a test node with PBR to the scene. In order for that node to not appear, I set the node's material's colorBufferWriteMask to an empty array (see this answer: ARKit hide objects behind walls) Then before I add my content I remove that node. Adding and removing this test node does the trick for me.
Here is an example:
var pbrTestNode: SCNNode!
func addPBRTestNode() {
let testGeometrie = SCNBox(width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0)
testGeometrie.materials.first?.diffuse.contents = UIColor.blue
testGeometrie.materials.first?.colorBufferWriteMask = []
testGeometrie.materials.first?.lightingModel = .physicallyBased
pbrTestNode = SCNNode(geometry: testGeometrie)
scene.rootNode.addChildNode(pbrTestNode)
}
func removePBRTestNode() {
pbrTestNode.removeFromParentNode()
}
func startSessionWithPlaneDetection() {
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
if #available(iOS 11.3, *) {
configuration.planeDetection = [.horizontal, .vertical]
} else {
configuration.planeDetection = .horizontal
}
configuration.isLightEstimationEnabled = true
// this prevents the delay when adding any nodes with PBR later
sceneController.addPBRTestNode()
// Run the view's session
sceneView.session.run(configuration)
}
Call removePBRTestNode() when you add your content to the scene.
Firstly
Get 3D model for AR app with no more than 10K polygons and a texture of 1K x 1K. The best result can be accomplished with 5K...7K polygons per each model. Totally, SceneKit's scene may contain not more than 100K polygons. This recommendation helps you considerably improve rendering performance and, I suppose, you'll have a minimal drop frame.
Secondly
The simplest way to get rid of drop frame in ARKit/SceneKit/AVKit is to use Metal framework. Just imagine: a simple image filter can be more than a hundred times faster to perform on the GPU than an equivalent CPU-based filter. The same things I could say about realtime AV-video and 3D animation – they perform much better on GPU.
For instance, you can read this useful post about using Metal rendering for AVCaptureSession. There's awesome workflow how to use Metal.
P.S. Check your animated object/scene in 3D authoring tool (if it's OK) before writing a code.

Making a node's sprite change as an animation (spriteKit)

im very new to swift, i have made a sprite kit game with a coin sprite. I want to make it spin so ive made 6 sprites in total. Im trying to get a continuous loop of spinning by quickly changing the sprites. I have tried to do this with the code below.
//This will hold all of the coin spinning sprites
let coinTextures : NSMutableArray = []
//There are 6 in total, so loop through and add them
for i in 0..<6 {
let texture : SKTexture = SKTexture(imageNamed: "Coin\(i + 1)")
coinTextures.insert(texture, at: i)
}
//When printing coinTextures, they have all been added
//Define the animation with a wait time of 0.1 seconds, also repeat forever
let coinAnimation = SKAction.repeatForever(SKAction.animate(with: coinTextures as! [SKTexture], timePerFrame: 0.1))
//Get the coin i want to spin and run the action!
SKAction.run(coinAnimation, onChildWithName: "coin1")
As i said im very new so im not sure what ive done wrong here.
Also the name of the coin i want to spin is "coin1" and the sprites so from coin1 to coin 6
You are almost there.
The problem is that your final line creates an action, but not running it on anything...
You got two alternatives:
1) Run your action on your scene
// Create an action that will run on a child
let action = SKAction.run(coinAnimation, onChildWithName: "coin1")
scene?.run(action)
or
2) Run the action directly on the child
// Assuming that you have a reference to coin1
coin1.run(coinAnimation)
As a sidenote, your array could be declared as var coinTextures: [SKTexture] = [], you can use append to add items to it and avoid the casting when you pass the textures to the action.
Or you can use a more compact form to construct your textures array:
let coinTextures = (1...6).map { SKTexture(imageNamed: "Coin\($0)") }
I hope that this makes sense

best way to detect if node touch the frame

Im building some game with SpriteKit that include balls as SKShapeNode. I create a class that define the balls and their properties (including SKPhysicsBody). the balls should run on the screen, and the frame is the screen border (by using edgeLoopFrom: self.frame). I also created a path node that is located at the top of the screen. now, I want to do that if some ball reach the top border of the frame so some function will execute.
I read some about it and i'm not sure what is the right way to do so, if by using contactBitMask or if there is another and better option.
If the right way is by contactBitMask - do I have to set a struct for the balls node or can I set it inside their class?
thanks!
If I'm getting this right, when a ball hits the path node that is located at the top half of the screen you want a function to be called.
First, I'm not sure if a path node is more efficient than a sprite node, in fact I have never really used path nodes, but here is what you can do.
Spritekit
check out the link above. What you need to do is implement the SKPhysicsContactDelegate. This will allow you to access the functions didBegin() and didEnd(). These functions are called when a contact is made within the physicsworld.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
func didEnd(_ contact: SKPhysicsContact) {
}
}
In order for these functions to be called, you need to set the physicsworld's contactDelegate to the class that will handle the calls. This would be your scene and a good place to set this is the didMove() function.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
Now when a contact happens that is detectable, your didBegin() will be called, and when the contact ends the didEnd() will be called.
Now we need to give our nodes some physicsbodies and we can set the different bitmasks on them in order to detect collisions/contacts. These are the 3 bitmasks we are concerned about:
categoryTestBitMask
collisionTestBitMask
contactTestBitMask
categoryTestBitMask: You can give nodes of similar type a category, example "ball" could be the category. All your different ball objects could have this same category. I use the "noCollision" category for when I want to detect a contact, but I don't want a collision to happen. You are limited to 32 different categories though so don't go crazy with tons of different ones.
collisionTestBitMask: Give your "ball" a category that you want a collision to happen with. Ex: set the collisiontestmask for your "ball" to the category bitmask of "wall". A collision is when 2 objects will physically run into each other; so your ball will bounce off the walls.
contactTestBitMask: a Contact is when 2 nodes overlap. So instead of the ball bouncing off something, it would call the contact method for our delegate. Note that you can set both the collision and contact bit masks to the same thing.
Now how do we set these masks. I use a Struct so that I can assign names to the bitmasks and set these 3 different masks with code. Something like this:
struct Mask {
static var ball: UInt32 = 0b10 //2
static var wall: UInt32 = 0b100 //4
static var pathNode: UInt32 = 0b1000 //8
}
now within code you can set the masks:
let ball = SKSpriteNode()
ball.name = "ball"
ball.physicsBody = SKPhysicsBody()
ball.physicsBody.categoryTestBitMask = Mask.ball
ball.physicsBody.collisionTestBitMask = Mask.wall
ball.physicsBody.contactTestBitMask = Mask.pathNode | Mask.wall
let pathNode = SKSpriteNode()
pathNode.name = "pathNode"
pathNode.physicsBody = SKPhysicsBody()
pathNode.physicsBody.categoryTestBitMask = Mask.pathNode
pathNode.physicsBody.collisionTestBitMask = 0
pathNode.physicsBody.contactTestBitMask = Mask.pathNode
Lets look at what we are saying here. We create a ball object and we set its category to "ball", we say we want it to have collisions with "wall" objects and we want our contact delegate functions to trigger with "pathNode" objects OR "wall" objects. Our pathNode object will have no collisions, and will have contacts with the ball.
Basically the ball will bounce off the walls, and will pass through the pathNode. It will call the contact delegate functions didbegin() and didend() with both the pathNode and wall objects.
Not finished yet... So when the function is called, how do we handle this? When the didbegin or didend function is called, it has a parameter of "contact". this contact param has 2 bodies to work with and these are the bodies that contacted each other. There are multiple ways we can handle this, but I'll just show you a simple way for now.
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA!.node!.name == "ball" {
// bodyA is our ball
switch contact.bodyB!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
else if contact.bodyB!.node!.name == "ball" {
// bodyB is our ball
switch contact.bodyA!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
}
Update:
What we are doing here is figuring out the type of bodyA and bodyB. So it starts with bodyA, if bodyA is a "ball", then we know that bodyA is a "ball and bodyB is the thing the ball came in contact with. We then use a switch statement to figure out what bodyB is. Once we know what bodyB is, we call the function that we need to call for that specific contact between these 2 nodes.
Then you just put your code into those specified functions of what you want to do.
This could be a lot to take in at once, If you are new I would suggest trying this out and trying to get it to work. After, I would youTube some videos on how to do this. It is a good idea to see how different people handle the same thing and then you can decide for your self on how to do it. This might not be the most elegant way of handling the contacts, but it works well and with some practice it will become second nature, Good luck!

Copying SCNParticleSystem doesn't seem to work well

I'm trying to use an SCNParticleSystem as a "template" for others. I basically want the exact same properties except for the color animation for the particles. Here's what I've got so far:
if let node = self.findNodeWithName(nodeName),
let copiedParticleSystem: SCNParticleSystem = particleSystemToCopy.copy() as? SCNParticleSystem,
let colorController = copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color],
let animation: CAKeyframeAnimation = colorController.animation as? CAKeyframeAnimation {
guard animation.values?.count == animationColors.count else {
return nil
}
// Need to copy both the animations and the controllers
let copiedAnimation: CAKeyframeAnimation = animation.copy() as! CAKeyframeAnimation
copiedAnimation.values = animationColors
let copiedController: SCNParticlePropertyController = colorController.copy() as! SCNParticlePropertyController
copiedController.animation = copiedAnimation
// Finally set the new copied controller
copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color] = copiedController
// Add the particle system to the desired node
node.addParticleSystem(copiedParticleSystem)
// Some other work ...
}
I copy not only the SCNParticleSystem, but also SCNParticlePropertyController and CAKeyframeAnimation just to be safe. I've found that I've had to manually do these "deep" copies "manually" since a .copy() on SCNParticleSystem doesn't copy the animation, etc.
When I turn on the copied particle system on the node it was added to (by setting the birthRate to a positive number), nothing happens.
I don't think the problem is with the node that I've added it to, since I've tried adding the particleSystemToCopy to that node and turning that on, and the original particle system becomes visible in that case. This seems to indicate to me that the the node I've added the copied particle system to is OK in terms of its geometry, rendering order, etc.
Something else perhaps worth mentioning: the scene is loaded from a .scn file and not created programmatically in code. In theory that shouldn't affect anything, but who knows ...
Any ideas on why this copied particle system doesn't do anything when I turn it on?
Do not use copy() method for particle systems !
copy() method does not allow copy particles' color (copied particles will be default white).
You can test it with the following code:
let particleSystem01 = SCNParticleSystem()
particleSystem01.birthRate = 2
particleSystem01.particleSize = 0.5
particleSystem01.particleColor = .systemIndigo // INDIGO
particleSystem01.emitterShape = .some(SCNSphere(radius: 2.0))
let particlesNode01 = SCNNode()
particlesNode01.addParticleSystem(particleSystem01)
particlesNode01.position.y = -3
sceneView.scene.rootNode.addChildNode(particlesNode01)
let particleSystem02 = particleSystem01.copy() // WHITE
let particlesNode02 = SCNNode()
particlesNode02.addParticleSystem(particleSystem02 as! SCNParticleSystem)
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)
Use clone() method for nodes instead !
clone() method works more consistently for 3d objects and particle systems and it can help you save particles' color but, of course, doesn't allow save a position for each individual particle.
let particlesNode02 = particlesNode01.clone() // INDIGO
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)