SKEmitterNode particles clumping together on creation - ios

I have an SKEmitterNode that is acting strange in different versions of iOS. If you look at the screenshot below, you will see that when my scene is loaded, the emitter I am using dumps an odd group of rain drops when the scene first loads.
The strangest part is that this happens in some versions of iOS like 8.1 and 9.2, but in some other versions like 9.0 and 7.0 it runs as expected. (meaning no initial clump of drops, just randomly spaced drops.)
Here is my code in didMoveToView:
let rainEmitterPath:NSString = NSBundle.mainBundle().pathForResource("homeScreenRain", ofType: "sks")!
self.rainEmitter01 = NSKeyedUnarchiver.unarchiveObjectWithFile(rainEmitterPath as String) as! SKEmitterNode
self.rainEmitter01.position = CGPointMake(device.x/2, device.y + 50)
self.rainEmitter01.targetNode = self
self.rainEmitter01.particleZPosition = 6.0
self.rainEmitter01.particleBirthRate = 300
self.rainEmitter01.particleSpeed = 5000 * worldScale
self.rainEmitter01.particleSpeedRange = worldScale * 1000
self.rainEmitter01.particleScale = worldScale
self.rainEmitter01.particleScaleRange = worldScale
self.rainEmitter01.particlePositionRange = CGVector(dx: device.x + 100, dy: 5)
//self.rainEmitter01.advanceSimulationTime(7.0)
self.addChild(rainEmitter01)
I have tried using the advanceSimulationTime method, but for some reason the group of drops still falls even after the delay.
I have also tried varying the y size of the position range because I figured that my height of 5 was causing this, but nothing changed even when I make the height equal to the size of the screen, and a height of 5 still works in other versions of iOS.
Anyone know why this is happening and a solution/workaround?
Thanks in advance.

Related

SpriteKit Particles — how to set state to have already fallen when scene loads?

I'm using SpriteKit particle emitter nodes for snow:
let snowEmitterNode = SKEmitterNode(fileNamed: "Snow.sks")
guard let snowEmitterNode = snowEmitterNode else { return }
snowEmitterNode.particleSize = CGSize(width: 4, height: 4)
snowEmitterNode.particleLifetime = 10
snowEmitterNode.particleBirthRate = 10
snowEmitterNode.xAcceleration = -gameSpeed
snowEmitterNode.particleLifetimeRange = 10
snowEmitterNode.position = CGPoint(x: (self.size.width/2), y: 800)
snowEmitterNode.zPosition = 80
addChild(snowEmitterNode)
It looks great, but it only begins once the scene loads, which means there is no snow at the start, and then it all suddenly starts to fall, which obviously looks very fake. Is it possible to somehow set the state of it to have already fallen partly on scene load to avoid this?
It is.
Use the advanceSimulationTime(_:) method to advance the emission of particles by the number of seconds you specify.
E.g. snowEmitterNode.advanceSimulationTime(5)
See https://developer.apple.com/documentation/spritekit/skemitternode/1398027-advancesimulationtime for details

Why are SKSpriteNodes only partially rendering?

I've coded a basic layout of "cards" for level selection in a game using Swift and SpriteKit. It's basically just 6 level selection cards side by side that have a picture of the level that the user can select. To create them I am running a for-loop and placing the first one in the center of the screen, a padding, then the second one, then padding etc. Each card is an SKSpriteNode from a png image. Each card is approx a 3rd of the device wide and about a 3rd of the devices height.
I create all six of them and then created an action that moves all 6 cards left or right to select which on the player would like. The one in the center is the one that is selected.
Everything works great on iphone simulators (tested on iPhone 6, iPhone 6 plus, iPhone 7 Plus and iPhone 5... all work great). On iPad simulators the first and last card have a portion of the image that doesn't render at all. The first card has about 1/4 of it lost on the left side, the last card has about 1/4 or it lost on the far right side. I tried running it on a physical iPad as well and it has the same issue. When I run it on an iPad Pro 12.7 it gets worse... it cuts off more of the image.
If I choose only to display 5 of the 6 cards they all render great.
If I choose to shrink them down to about 1/4 of the device width and 1/4 of the device height and lessen the padding they render fine.
I tried playing with the Scene and View sizes and scales and didn't have any improvement.
I've tried using different images and there is no changes at all.
I've double checked all zPositions and found no improvement.
I've tried systematically removing all other objects in the scene and still have the problem.
I've put them on their own "layer" which is an SKEffectNode named cardNode. (it's an SKEffectNode because I choose to later blur it when an alert screen comes up in front of it). I thought that putting them onto their own layer might help but it didn't.
I've put physics bodies on the cards just to make sure that they are still "there" and the physics bodies appear in the correct places. If I click on part of the node that isn't rendered it still does behave properly as though it was still rendered in that area.
I can't figure out where to go from here to fix this. Ideally I would like to add more cards yet in the future but getting stuck on this problem.
Here is the code that I have for creating the cards.
let cardNode = SKEffectNode()
let levelCardArray: [String] = [
"BlackBoxLevelCard.png"
,"FruitLevelCard.png"
,"SportsLevelCard.png"
,"BarnLevelCard.png"
,"SeaLevelCard.png"
,"SpaceLevelCard.png"
]
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
let w10 = screenWidth * 0.10
let w40 = screenWidth * 0.40
let w50 = screenWidth * 0.50
let w60 = screenWidth * 0.60
let h50 = screenHeight * 0.50
let cardMargin = w10
let cardSize = CGSize(width: w40, height: w60)
let startPosition = CGPoint(x: w50, y: h50)
override func didMove(to view: SKView) {
let scene = levelSelectionScene(size: view.bounds.size)
scene.backgroundColor = UIColor.black
let skView = view as SKView
skView.ignoresSiblingOrder = true
cardNode.zPosition = 100
self.addChild(cardNode)
for i in 1...levelCardArray.count {
let currentArrayValue = i - 1
let cardSprite = SKSpriteNode(imageNamed: levelCardArray[currentArrayValue])
cardSprite.size = cardSize
cardSprite.position = CGPoint(x: startPosition.x + (CGFloat(currentArrayValue) * (cardSize.width + cardMargin)), y: startPosition.y)
cardSprite.zPosition = cardNode.zPosition
cardSprite.name = "levelCardObject"
cardNode.addChild(cardSprite)
}
Any help or insight would be greatly appreciated. Thanks guys!
I tried all day today to try to find a way to make this work through resizing the scenes and views and haven't had any luck with it at all.
My solution is to check for the device type and if it's an iPad I am reducing the image sizes and buffer between images until it doesn't cut them off. I don't consider this a very good solution, really just a work around until I can find a better way to do it. Thank you guys for your thoughts though. I definitely appreciate it!

SpriteKit - Basic Positioning of a label in a background

Very new to Sprite Kit and i'm doing some reading now, but wanted to ask about something that I haven't found the best answer to yet.
I'm doing a tutorial with some code that creates a background, and then adds a label to show a score. I started changing the label code to position it on the top-left corner of the screen.
Here is the code (with my edits to the label, gameLabel):
let screenSize = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: screenWidth / 10, y: (screenHeight / 15) - 100)
background.blendMode = .Replace
background.zPosition = -1
addChild(background)
gameScore = SKLabelNode(fontNamed: "Chalkduster")
gameScore.text = "Score: 0"
gameScore.position = CGPoint(x: screenWidth / 10, y: (screenHeight / 15) - 100)
gameScore.horizontalAlignmentMode = .Left
gameScore.verticalAlignmentMode = .Top
gameScore.fontSize = 48
addChild(gameScore)
So the label is not displaying in any case, and my assumption is this:
Because i'm adding the background first, the label is getting added within the confines of the background, therefore it needs to be positioned differently, perhaps using the label size instead of the screen size?
My questions:
How can I get the label to always appear in the top left?
The author chose a hard-coded CGPoint for this background image and then said it has to be on an iPad, but i'd like to do it for iPhone 6/plus in landscape as well as iPad. Is there a way I can just make it work on both devices without having to specify a CGPoint like that? Can't it just scale and work regardless?
Thanks and apologies if these are basic questions - i'm going to do my best to continue reading on the subject..
Your question has a simple answer to it. You have forgotten and missed out somethings in your code, and I can show you these things.
In your code you set the background ZPosition to -1, the smallest number will always appear at the back. So if you set the SKLabelNode to a bigger zPosition it will always appear at the front, as maybe there may be a problem with rendering, as I have also experience like these, I fix it this way:
Before you add the LabelNode set it's property to this:
gamescore.zPosition = 0
0, In this case could just be anything bigger than the backgrounds(or the node that you want to appear at the back). So this just tells the compiler that you want the LabelNode to appear at the front, or in front of the Background.
If you want to make a universal app or game, with SpriteKit you will need to add some extra code to your game or app. Since I think that it is better to give you a good tutorial to show you instead of showing you some basics, I will give you a good link on how to do this:
SpriteKit: how to make a universal app
I hope this helps, and don't worry this took me some time to figure out my self.

Strange bug/artefacts on SCNNode rendering

On some iOS devices (iPhone 6s Plus) there is a partial and arbitrary disappearance of object parts.
How to avoid this?
All sticks must be the same and are clones of one SCNNode.
16 complex SCNNodes, from 3 SCNNode: box, ball and stick. Node containing a geometry by node.flattenedClone().
It must be like this:
Сode fragment:
func initBox()
{
var min: SCNVector3 = SCNVector3()
var max: SCNVector3 = SCNVector3()
let geom1 = SCNBox(width: boxW, height: boxH, length: boxL, chamferRadius: boxR)
geom1.firstMaterial?.reflective.contents = UIImage(data: BoxData)
geom1.firstMaterial?.reflective.intensity = 1.2
geom1.firstMaterial?.fresnelExponent = 0.25
geom1.firstMaterial?.locksAmbientWithDiffuse = true
geom1.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let geom2 = SCNSphere(radius: 0.5 * boxH)
geom2.firstMaterial?.reflective.contents = UIImage(data: BalData)
geom2.firstMaterial?.reflective.intensity = 1.2
geom2.firstMaterial?.fresnelExponent = 0.25
geom2.firstMaterial?.locksAmbientWithDiffuse = true
geom2.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let geom3 = SCNCapsule(capRadius: stickR, height: stickH)
geom3.firstMaterial?.reflective.contents = UIImage(data: StickData)
geom3.firstMaterial?.reflective.intensity = 1.2
geom3.firstMaterial?.fresnelExponent = 0.25
geom3.firstMaterial?.locksAmbientWithDiffuse = true
geom3.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let box = SCNNode()
box.castsShadow = false
box.position = SCNVector3Zero
box.geometry = geom1
Material.setFirstMaterial(box, materialName: Materials[boxMatId])
let bal = SCNNode()
bal.castsShadow = false
bal.position = SCNVector3(0, 0.15 * boxH, 0)
bal.geometry = geom2
Material.setFirstMaterial(bal, materialName: Materials[balMatId])
let stick = SCNNode()
stick.castsShadow = false
stick.position = SCNVector3Zero
stick.geometry = geom3
stick.getBoundingBoxMin(&min, max: &max)
stick.pivot = SCNMatrix4MakeTranslation(0, min.y, 0)
Material.setFirstMaterial(stick, materialName: Materials[stickMatId])
box.addChildNode(bal)
box.addChildNode(stick)
boxmain = box.flattenedClone()
boxmain.name = "box"
}
Add nodes to the scene:
func Boxesset()
{
let Boxes = SCNNode()
Boxes.name = "Boxes"
var z: Float = -4.5 * radius
for _ in 0..<4
{
var x: Float = -4.5 * radius
for _ in 0..<4
{
let B: SCNNode = boxmain.clone()
B.position = SCNVector3(x: x, y: radius, z: z)
Boxes.addChildNode(B)
x += 3 * Float(radius)
}
z += 3 * Float(radius)
}
self.rootNode.addChildNode(Boxes)
}
This is tested and works great on the simulator - all devices,
on the physical devices - iPad Retina and iPhone 5.
Glitch is observed only at ultra modern iPhone 6s Plus (128 Gb).
The problem is clearly visible on the video ->
The problem with graphics can be solved by changing the Default rendering API to OpenGL ES...
...but you may have unexpected problems in pure computing modules that are not associated with graphics on iPhone 6S Plus. (the iPhone 6 has no such problems).
What's wrong?
TL;DR
Add scnView.prepareObject(boxmain, shouldAbortBlock: nil) to the end of your initBox.
I had a quick look at your code running on my 6s Plus and saw similar results. One of the corner nodes was missing, and was consistently missing each run. But we're not running the same code, mine's missing the materials data...
SceneKit is lazy, often things are not done until an object is added to a scene. I first came across this extracting geometry from a SceneKit primitive (SCNSphere etc), you're finding it when you clone a clone of something via the following lines.
let B: SCNNode = boxmain.clone()
...
boxmain = box.flattenedClone()
I'd say SceneKit is simply not completing the clone before the second clone occurs consistently. I have no way of knowing this for sure.
Removing the first clone fixes this issue for me. For example replace boxmain = box.flattenedClone() with boxmain = box. But I'd say what you've done is best practice, flattening these nodes will reduce the number of draw calls and improve performance (probably not an issue on the 6s).
SceneKit also provides a method - prepareObject:shouldAbortBlock: that will perform the operations required before an object is added to a scene (in this case the .flattenedClone()).
Adding the following line to the end of your initBox function also fixes the problem and is a better solution.
scnView.prepareObject(boxmain, shouldAbortBlock: nil)
Just say, I don't know the right answer to my question, but I found an acceptable solution for myself.
It turned out, it's all in the "diffuse" property of the SCNMaterial.
For whatever reason, Metal does not very like when diffuse = UIColor(...)
But if at least one element in a compound SCNNode (as in my case) is diffuse.contents = UIImage(...), then everything begins to work perfectly.
it works
diffuse=<SCNMaterialProperty: 0x7a6d50a0 | contents=<UIImage: 0x7a6d5b40> size {128, 128} orientation 0 scale 1.000000>
it doesn't work
diffuse=<SCNMaterialProperty: 0x7e611a50 | contents=UIDeviceRGBColorSpace 0.25 0.25 0.25 0.99>
I have found the solution of the problem is simply:
I just added one small, inconspicuous element with diffuse.contents = UIImage(...) to the existing 3 elements with diffuse.contents = UIColor(...) and it worked great.
So, my recommendations:
be careful when working with Metal. (I have a problems on the 5S devices and above)
thoroughly test the SceneKit applications on real devices, don't trust only the simulator
I hope, it's temporary bugs and it will be fix in future releases of Xcode.
Have a nice apps!
P.S. By the way, the finished app is completely free in the AppStore now
Qubic: tic-tac-toe 4x4x4

Cocos2d V3.4 Chipmunk RELEASE mode display bug

there seems to be a Cocos2dV3.4 display bug when adding lots of Chipmunk physics bodies quickly in the same spot in RELEASE MODE on a device. If the physics body is a rectangle its happens straight away, if it is a circle it takes longer but does happen. The key point is it always works fine in DEBUG mode but errors occur in RELEASE mode on the device. It always works fine in the simulator. (just Edit Scheme.. to change to release mode for RUN)
Example swift code to replicate. (simply add a dynamic polygon physics body at the same point quickly)
class MainTestScene: CCNode
{
var _rootPhysicsNode : CCPhysicsNode!
var count : CCTime = 0
override init()
{
super.init()
_rootPhysicsNode = CCPhysicsNode()
_rootPhysicsNode.gravity = ccp(0,-100)
self.addChild(_rootPhysicsNode)
}
override func update(delta: CCTime)
{
count += delta
if count > 0.2
{
count = 0
for var i = 0; i <= 10 ; i++
{
let item = CCSprite(imageNamed: "ccbResources/rainbowblinky.png")
item.anchorPoint = ccp(0.5,0.5)
item.scale = 0.3
item.position = ccp(CCDirector.sharedDirector().viewSize().width / 2, CCDirector.sharedDirector().viewSize().height / 2 )
item.physicsBody = CCPhysicsBody(rect: CGRectMake(0, 0, 100, 100), cornerRadius: 1)
_rootPhysicsNode.addChild(item)
}
}
}
}
In DEBUG mode you get sprites falling from a point and pushing each other out. (see picture) In RELEASE MODE on an iOS device the screen flicks every second shows a single sprite and slows down to a crawl.
Can someone help? Am I doing something wrong?
B

Resources