I noticed that when application using SceneKit runs the memory usage increase
steadily. After some search I pin pointed it to aNode.runAction.
Every time runAction is called it gets a bit of memory space and never release it.
Because runAction is used often in the application it is obvious that it is going to crash it.
Is there something to do to avoid that problem?
I am using this kind of functions to move some nodes.
I tried moving one node but nothing changed it takes less memory that is all.
func moveMyNodes(x:CGFloat, y:CGFloat, z: CGFloat, speed: CGFloat) {
for k in 0..myNodes.count {
let action = SCNAction.moveBy(x:x, y:y, z:z, duration: speed)
myNodes[k].runAction(action)
}
}
I found the reason.
In loop some of the nodes did not have geometry because they should not be seen.
When .runAction executed for a node without geometry it takes a bit of memory and they add up.
I've just get rid of nodes without geometry.
Related
I was experimenting a little with Sprite Kit, and I noticed that when I let my app run for a while it crashes.
What I'm trying to do, is to draw a line that follows my player movement, I did this like so:
var ref: CGMutablePathRef = CGPathCreateMutable()
var shapeLine: SKShapeNode!
override func update (){
shapeLine.removeFromParent()
CGPathAddLineToPoint(ref, nil, player.position.x, player.position.y)
shapeLine = SKShapeNode(path: ref)
shapeLine.lineWidth = 3
shapeLine.path = ref
addChild(shapeLine)
}
And this works pretty fine, I remove the node everytime, updating the path, and creating another node with the new path.
The point is that after some seconds (around 25) it crashes.
The nodes are constant since I add and remove everytime one. The framerate starts to decrease right before crashing and goes from 30fps to 20fps.
What I find strange is the use of memory, it increases a lot, getting even to use around 800mb before crashing.
Is there something I'm forgetting, or is it just that the Path increases too much to be handled?
Unfortunately SKShapeNode is bugged, there are many bugs around this library component, most of these are reported here.
You can convert a SKShapeNode to a texture (textureFromNode) than use it with SKSpriteNode.
I am making a sprite kit game and I am using the plist file to set properties of each level. One of the properties in my plist file is a dictionary called patterns, which contains n items, where each of the items is a block, with hand typed x and y positions. This model is working perfectly fine for the kind of game I am making, as it is very convenient to set the levels right in a quick manner. However, I am facing one drawback I cant solve myself due to the lack of coding experience: some of the levels have as many as 290 blocks, so when the app tries to read the level, the app freezes for like 5 seconds. This is very annoying for the user. At the beginning my approach was: Read the plist file, and for each item call the method which creates the block as a SKSpriteNode using its imageNamed "" method. I thought this is the reason it lags so much, the fact that I am trying to load 300 sprites at the runtime seemed as a promising cause of the problem. Then I tried the following: I made the method which loads a pool of block initially, when the game starts for the first time. This is my method for that
func addObsticles1ToPool() {
for i in 0...300 {
let element = SKSpriteNode(imageNamed: "obsticle1")
element.hidden = true
obsticle1Pool.append(element)
}
}
Then, my code reads the plist file, and for each of the block calls the following:
func block(x: CGFloat, y: CGFloat, movingUp: Bool, movingSide: Bool, spin: Bool, type: Int16) {
var block: SKSpriteNode!
for obs in obsticle1Pool {
if obs.hidden {
block = obs
break
}
}
block.hidden = false
// Further down the properties of the block are set, such as actions it should perform depending on the input values, also its physics body is set.
I also have methods handling the fact that new elements should be added to the pool as game the proceeds and all that works just fine. The lag time dropped to around 3.5 - 4 secs, but that is still not good enough obviously. I would like to have a game with no lag. However, I am not sure if there is another, more efficient way, to do what I am trying to do than using the sprites pool.
Does anyone know how to reduce this lag time?
I have had the same problem! The issue is in this line...
let element = SKSpriteNode(imageNamed: "obsticle1")
SpriteKit isn't smart enough to know that a texture was already created with that image. So what it is doing is creating that texture over and over again and that is expensive.
Instead create a texture outside of the loop first and then create the sprite node with a texture. Something like this...
let elementTexture = SKTexture(imageNamed: "objstical1")
for i in 0...300 {
let element = SKSpriteNode(texture: elementTexture)
element.hidden = true
obsticle1Pool.append(element)
}
Not only will this be a ton faster it will decrease your apps memory a ton...assuming it was the same issue I was having. Hopefully that helps.
Is it possible to vary the speed of an object moving because of SKAction.followPath? For instance, let's say we use the code below to have a ball follow a rectangular path. The code will use a constant speed throughout the path. But what if we want to vary the speed of the object along the rectangle?
let goPath = SKAction.followPath(ballPath!.CGPath, duration: 2.5)
movingBall.runAction(goPath)
Is the only option to effectively have the ball follow a rectangular path built of separate lines with different speeds (as opposed to one path)?
Thanks!
two ways of accomplishing this.
FIRST WAY:
You can use SKAction.speedBy. You'd group SKAction.speedBy with your followPath. example:
movingBall.runAction(SKAction.group([
SKAction.followPath(ballPath!.CGPath, duration: 2.5)
SKAction.speedBy(4, duration: 2.5)
]))
Now the ball is going to reach 4 times the speed over 2.5 seconds. NOTE: now we're not going to be honoring the followPath duration. As time passes we're increasing the speed of the sprite. It's going to reach its destination in less than 2.5 seconds.
SECOND WAY:
The other way is to use a timing function. This is probably the better way to go because we have a lot more control over how fast the ball is going to move during the animation.
example:
let goPath = SKAction.followPath(ballPath!.CGPath, duration: 2.5)
goPath.timingFunction = {
t in
return powf(t, 3)
}
movingBall.runAction(goPath)
The way a timing function works is that it gives you a block that has the current elapsed time as a float. it's your job to use some kind of computation to modify the value that comes in, and then return it.
I'm just cubing whatever time comes into the function and returning it. This makes it so the ball starts off very slow, and then accelerates very quickly towards the end of the animation.
You can get really creative and use sin waves to make things bounce, etc. Just note that any returned values <0 are ignored.
With the followPath SKAction this isn't possible.
What you could do though is use the update method of the node to update its position manually.
This will take a bit of work to make it follow a path but you can do a lot more than is available with SKAction.
I have been working on an app in SpriteKit that displays a new image after every gesture from the user. During each session, the images are inevitably removedFromParent() but the user calls them frequently.
After using my app for about 6 minutes though, I noticed that it starts to get...choppy. The gestures I have set up use swiping but for some reason when I tap the screen a lot the CPU usage shoots up to 104%?!?
After every swipe, I call this code:
var chooseImage:String = imageCollection[Int(arc4random() % 16)]
var swiperImage = SKSpriteNode(imageNamed: chooseImage)
swiperImage.position = nodePosition
swiperImage.xScale = SH * 0.002
swiperImage.yScale = SH * 0.002
addChild(swiperImage)
This is part of the code that gets called after every swipe I make. Could this be a memory leak? I am at a total loss.
This is is the CPU usage result:
Instead of storing an array of Strings you should try storing the SKTextures for those images. These textures would be created when the object you referenced code from is instantiated. Then you can use those textures to instantiate the nodes later, reducing the amount of unnecessary image loading each time a node is created. It would look something like:
let imageTextures = imageCollection.map { SKTexture(imageNamed: $0) }
...
let swiperImage = SKSpriteNode(texture: imageTextures[Int(arc4random_uniform(imageTextures.count))]
Additionally, you can use arc4random_uniform() to generate a "uniformly distributed random number less than upper_bound" instead of using the mod.
This solution also has the benefit of letting you have multiple nodes use the same texture without having to reload it for each node.
Why don't you clear the child of your parents before you add a new one.
I'm making multilevel game based on SpriteKit.
Everything works well except one thing: when user plays long time, changes many levels, etc... then SpriteKit starts losing textures.
I mean there is no big red cross like when image load fails but just empty space like nothing is there.
Hours of debugging and googling did not produce any results.
How can I deal with that bug?
I think I might be having a related issue, except the loss of textures occurs when I am rapidly running actions on a SKSpriteNode. In my code, I run an action each time I get a touch and when the touches are rapid and the animations are firing quickly, the base texture of the SKSpriteNode seemingly disappears. No memory warnings, not a peep from the console; the SKSpriteNode's texture is magically set to nil.
I get the impression from your question that this isn't your exact cause, but you are having the same symptoms. Unfortunately I don't know what is causing it. What I've done to work around the issue has been to constantly check if the texture on my SKSprite node has been set to nil immediately after I run an SKAction and then re-assign it if needed.
So, an abridged version (in Swift) of what I'm doing looks like this :
func doAnimation( ) {
_character.runAction(someSKAction, withKey: "animation")
//Whoops!, we lost our base texture again!
if _character.texture == nil {
let atlas = SKTextureAtlas(named: "someAtlasName")
let texture = atlas.textureNamed("idleFrame" )
_character.texture = texture
}
}
This is not really solution so much as a workaround, but it might be adaptable to your situation until you (or someone else on SO) figures it out. I won't argue that it's not ugly.
BTW, you are not alone with the disappearing texture issue; I wrote a similar response to a similar question here.