Different SKTexture causes insane lag - swift

I tried making two identical characters (two classes that extend from SKSpriteNode) for the game, the only difference is the SKTexture they're using. I'll refer to them as A and B. Whenever I let A shoot a bullet, it doesn't lag but when I let B shoot a bullet, it lags just a little bit. The problem became clear when I spam shoot from A, it stays at a constant 60 FPS but when I spam shoot from B, it lags, down to around 50 FPS.
A's texture is "Lampy.png" and I tried setting B's texture as "Lampy2.png". Those 2 images are exactly the same, the only difference is the file name, but B lags.
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy2")
}
Code above is from B and it lags
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
Code above is from A and it does not lag
Other parts of the two classes are exactly the same. Did I import the images incorrectly or something? I can't remember how I imported Lampy but for Lampy2 I dragged it directly from Finder to the assets folder.
Edit - Here's the full class code
import Foundation
import SpriteKit
class Lampy: Player{
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func shoot(){
let thiccBullet = Thicc()
thiccBullet.position = position
thiccBullet.bulletBody.zRotation = playerBody.zRotation
thiccBullet.owner = self;
parent?.addChild(thiccBullet)
}
}
At the line with playerBody.texture = SKTexture(imageNamed: "Lampy")
if I change Lampy to Lampy2 then it lags.

Related

SpriteKit not deallocating all used memory

I have ready many (if not all) articles on SO and other sites about the disasters of dealing with SpriteKit and memory issues. My problem, as many others have had, is after i leave my SpriteKit scene barely any of the memory added during the scene session is released. I've tried to implement all suggested solutions in the articles i've found, including, but not limited to...
1) Confirm the deinit method is called in the SKScene class.
2) Confirm no strong references to the parent VC in the scene class.
3) Forcefully remove all children and actions, and set the scene to nil when the VC disappears. (Setting the scene to nil was what got the deinit method to eventually get called)
However, after all of that, memory still exists. Some background, this app goes between standard UIKit view controllers and a SpriteKit scene (it's a professional drawing app). As an example, the app is using around 400 MB before entering a SpriteKit scene. After entering the scene and creating multiple nodes, the memory grows to over 1 GB (all fine so far). When i leave the scene, the memory drops maybe 100 MB. And if i re-enter the scene, it continues to pile on. Are there any ways or suggestions on how to completely free all memory that was used during a SpriteKit session? Below is a few of the methods being used to try and fix this.
SKScene class
func cleanScene() {
if let s = self.view?.scene {
NotificationCenter.default.removeObserver(self)
self.children
.forEach {
$0.removeAllActions()
$0.removeAllChildren()
$0.removeFromParent()
}
s.removeAllActions()
s.removeAllChildren()
s.removeFromParent()
}
}
override func willMove(from view: SKView) {
cleanScene()
self.removeAllActions()
self.removeAllChildren()
}
Presenting VC
var scene: DrawingScene?
override func viewDidLoad(){
let skView = self.view as! SKView
skView.ignoresSiblingOrder = true
scene = DrawingScene(size: skView.frame.size)
scene?.scaleMode = .aspectFill
scene?.backgroundColor = UIColor.white
drawingNameLabel.text = self.currentDrawing?.name!
scene?.currentDrawing = self.currentDrawing!
scene?.drawingViewManager = self
skView.presentScene(scene)
}
override func viewDidDisappear(_ animated: Bool) {
if let view = self.view as? SKView{
self.scene = nil //This is the line that actually got the scene to call denit.
view.presentScene(nil)
}
}
As discussed in the comments, the problem is probably related to a strong reference cycle.
Next steps
Recreate a simple game where the scene is properly deallocated but some of the nodes are not.
I'll reload the scene several time. You'll see the scene is properly deallocated but some nodes into the scene are not. This will cause a bigger memory consumption each time we replace the old scene with a new one.
I'll show you how to find the origin of the problem with Instruments
And finally I'll show you how to fix the problem.
1. Let's create a game with a memory problem
Let's just create a new game with Xcode based on SpriteKit.
We need to create a new file Enemy.swift with the following content
import SpriteKit
class Enemy: SKNode {
private let data = Array(0...1_000_000) // just to make the node more memory consuming
var friend: Enemy?
override init() {
super.init()
print("Enemy init")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("Enemy deinit")
}
}
We also need to replace the content of Scene.swift with the following source code
import SpriteKit
class GameScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
print("Scene init")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("Scene init")
}
override func didMove(to view: SKView) {
let enemy0 = Enemy()
let enemy1 = Enemy()
addChild(enemy0)
addChild(enemy1)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let newScene = GameScene(size: self.size)
self.view?.presentScene(newScene)
}
deinit {
print("Scene deinit")
}
}
As you can see the game is designed to replace the current scene with a new one each time the user taps the screen.
Let's start the game and look at the console. Will' see
Scene init
Enemy init
Enemy init
It means we have a total of 3 nodes.
Now let's tap on the screen and let's look again at the console
Scene init
Enemy init
Enemy init
Scene init
Enemy init
Enemy init
Scene deinit
Enemy deinit
Enemy deinit
We can see that a new scene and 2 new enemies have been created (lines 4, 5, 6). Finally the old scene is deallocated (line 7) and the 2 old enemies are deallocated (lines 8 and 9).
So we still have 3 nodes in memory. And this is good, we don't have memory leeks.
If we monitor the memory consumption with Xcode we can verify that there is no increase in the memory requirements each time we restart the scene.
2. Let create a strong reference cycle
We can update the didMove method in Scene.swift like follows
override func didMove(to view: SKView) {
let enemy0 = Enemy()
let enemy1 = Enemy()
// ☠️☠️☠️ this is a scary strong retain cycle ☠️☠️☠️
enemy0.friend = enemy1
enemy1.friend = enemy0
// **************************************************
addChild(enemy0)
addChild(enemy1)
}
As you can see we now have a strong cycle between enemy0 and enemy1.
Let's run the game again.
If now we tap on the screen and the look at the console we'll see
Scene init
Enemy init
Enemy init
Scene init
Enemy init
Enemy init
Scene deinit
As you can see the Scene is deallocated but the Enemy(s) are no longer removed from memory.
Let's look at Xcode Memory Report
Now the memory consumption goes up every time we replace the old scene with a new one.
3. Finding the issue with Instruments
Of course we know exactly where the problem is (we added the strong retain cycles 1 minute ago). But how could we detect a strong retain cycle in a big project?
Let click on the Instrument button in Xcode (while the game is running into the Simulator).
And let's click on Transfer on the next dialog.
Now we need to select the Leak Checks
Good, at this point as soon as a leak is detected, it will appear in the bottom of Instruments.
4. Let's make the leak happen
Back to the simulator and tap again. The scene will be replaced again.
Go back to Instruments, wait a few seconds and...
Here it is our leak.
Let's expand it.
Instruments is telling us exactly that 8 objects of type Enemy have been leaked.
We can also select the view Cycles and Root and Instrument will show us this
That's our strong retain cycle!
Specifically Instrument is showing 4 Strong Retain Cycles (with a total of 8 Enemy(s) leaked because I tapped the screen of the simulator 4 times).
5. Fixing the problem
Now that we know the problem is the Enemy class, we can go back to our project and fix the issue.
We can simply make the friend property weak.
Let's update the Enemy class.
class Enemy: SKNode {
private let data = Array(0...1_000_000)
weak var friend: Enemy?
...
We can check again to verify the problem is gone.

SKPhysicsBody rises CPU usage

I'm just about done with a game that is based on physics. I noticed that Xcode is saying the energy impact is "Very High" which makes sense because the CPU usage is about 21-26%. (I'm using an iPhone 6 in the release configuration, not the simulator) I figured out where the problem seems to be which is the SKPhysicsBody of each node. When I comment out the initialization of the physics body the cpu usage drops to 4-7% and energy impact reads "Low". Can someone give me some insight on what to do?
class Item:SKSpriteNode {
var id:[Int]
init(type: String, ID: [Int]) {
self.id = ID
var newTexture = SKTexture(imageNamed: type)
super.init(texture: newTexture, color: UIColor(), size: newTexture.size())
self.name = type
setPhysicsBody()
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func setPhysicsBody() {
physicsBody = SKPhysicsBody(circleOfRadius: 3)
physicsBody?.isDynamic = false
physicsBody?.categoryBitMask = bitMask.obstacleCategory
physicsBody?.contactTestBitMask = bitMask.ballCategory
physicsBody?.collisionBitMask = bitMask.ballCategory
physicsBody?.restitution = 0.05
physicsBody?.friction = 0.1
}
}
You can help ease the CPU load by setting the SKPhysicsBody's contactTestBitMask and collisionBitMask. By default, the CPU is keeping track of each physics body and its relation to all other physics bodies. By setting the contactTestBitMask and collisionBitMask, you can tell the CPU to only worry about the objects you need the physics body to interact with.

GKEntity and state machines

I'm working on a small project using SpriteKit and GameplayKit. It's my first time using an entity/component system and I'm loving it. But now, I'm at a point where I need to keep track of my entities states (Spawning, Normal, Removing) so they don't interact during their spawning phase (which may or may not include actions to animate the thing) and their removing phase.
So far, I've created a EntityStateComponent which instanciate a GKStateMachine with the different states and since there is no need for per-frame updates it's not that complicated. The thing is that this state is more entity related than component related and I'm wondering if it would make sense to subclass from GKEntity and add the state machine in there instead of in a component.
Your thoughts?
PS: I'm already sub-classing from GKEntity just to have a convenience init() that creates all the components
You are right, state is entity related, not component related. Put the state machine into the entity directly, or create an entity base class that all your entities inherit from.
class VisualEntityBase : GKEntity, VisualEntity {
var node: SKSpriteNode!
var stateMachine: GKStateMachine!
// MARK: Initialization
init(imageNamed: String, atStartPosition: CGPoint) {
super.init()
// Initialise Texture
let texture = SKTexture(imageNamed: imageNamed)
// Initialise Node
self.node = SKSpriteNode(texture: texture, size: texture.size())
self.node.position = atStartPosition
// Initialise StateMachine
self.stateMachine = GKStateMachine(states: [
VisualEntityIdle(),
VisualEntityPendingMove(),
VisualEntityMoving()
])
self.stateMachine.enter(VisualEntityIdle.self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

is it a good practice to create an SKSpriteNode within a subclassed SKSpriteNode?

This code here subclasses SKSpriteNode and initializes it that accepts SKScene
import SpriteKit
class Spaceship: SKSpriteNode{
var spaceship:SKTexture
var hitpoint = 100
var thescene:SKScene
var lazer5:SKSpriteNode?
var lazer5_pathofdestruction:SKSpriteNode?
init(skScene:SKScene) {
thescene = skScene
self.spaceship = SKTexture(imageNamed:"Spaceship")
super.init(texture: spaceship, color: SKColor.clearColor(), size: spaceship.size())
self.name = "Spaceship"
self.setScale(0.10)
self.position = CGPointMake(CGRectGetMidX(skScene.frame), CGRectGetMidY(skScene.frame))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
.......
This Class (Spaceship) has a method that will fire the dualcanon:
func firedualcanon() {
let canonposition = [10.00 , -10.00]
let fireSound = SKAction.playSoundFileNamed("dualcanon.wav", waitForCompletion: false)
let targeting = SKAction.sequence([
SKAction.runBlock{
for position in canonposition {
let canon = SKSpriteNode(color: SKColor.whiteColor(), size: CGSize(width:4, height: 3))
canon.name = "weapon"
canon.position = CGPointMake(self.position.x - CGFloat(position), self.position.y)
let projectile = SKAction.moveTo(CGPoint(x: self.position.x - CGFloat(position), y: self.thescene.frame.height + 200 ),duration: 1.50)
GlobalReference.setPhysicsBody(canon,collidertype: GlobalVariable.ColliderType.Light)
self.thescene.addChild(canon)
let bulletaction = SKAction.sequence([projectile,SKAction.removeFromParent()])
canon.runAction(bulletaction)
}
}
, SKAction.waitForDuration(0.10)
])
self.thescene.runAction(
SKAction.repeatActionForever(SKAction.group([fireSound,targeting])),
withKey: "fireweapons")
}
as you can see in the initialization I used the SKTexture but now in the method firedualcanon() I created a canon using SKSpriteNode.
Is this a good Swift Programming practice?
When coding sprite-based games, it is quite common practice to have your game objects be subclasses of the sprite class (or more generic nodes), even before Swift and SpriteKit (e.g., Cocos2d/Objective-C).
Some purist might argue that you should decouple the model (data), views (sprites) and controllers (game logic) into separate objects, but in simple games that can lead to having a huge number of classes, each of which does very little.
(In my opinion, it is really about preference and what is convenient for your particular app)
If you still wish to go in that direction, you could have each object's logic/state represented by a non-SpriteKit class (e.g., Swift root class or subclass of NSObject), with each object somehow linked to the sprite that represents it on screen (a reference, unique id, etc.), the details up to you.
Then, on each frame, update the visual state (position, etc.) of each sprite based on the logical (game) state of the model object (e.g., "spaceship") they represent.
Hope it makes sense.
I agree with NicolasMiari. It depends on your particular game but it's usual to have a SKNode class that consist on one, two or more SKSpriteKitNode in order to represent it properly. For example, what if your spaceship can have a little spaceship as a satellite with its particular actions, animations, collisions etc? In cases like that is easier to have it a as separate sprite.

iOS SpriteKit Collision Detection Fails After Decode Save

I have a game where Falling Nodes fall down and hit the Base Number Sprite Node and game logic is run from there. When I set up a new game from scratch the collision detection works exactly how it should. My problem occurs when I create a game from a previous save using NSCoding. In both cases (new game and continue from save game) the physics bodies are the same - dynamic, same size body, same contactTestBitMask, same categoryBitMask. I have tested all of this so I know it is true. The physics contact delegate is also set to the right object. In a game continued from a save, however, the contacts are not registered and I cannot figure out why. The only thing I can think of, but am unable to figure out is object which is set as my physics contact delegate and is the parent of the objects I want collision detection for gets loaded/unarchived without me actually calling decodeObjectForKey for it.
Any help would be much appreciated
func initBaseNumberSpritePhysicsBody() {
baseNumberSprite.physicsBody = nil
baseNumberSprite.physicsBody = SKPhysicsBody(rectangleOfSize: baseNumberSprite.size)
baseNumberSprite.physicsBody!.categoryBitMask = baseNumberCategory
baseNumberSprite.physicsBody!.contactTestBitMask = fallingNodeCategory
baseNumberSprite.physicsBody!.collisionBitMask = 0
baseNumberSprite.physicsBody!.usesPreciseCollisionDetection = true
baseNumberSprite.physicsBody!.allowsRotation = false
}
func initPhysicsBodyForFallingNode(node: NumberNode) {
node.physicsBody = nil
node.physicsBody = SKPhysicsBody(rectangleOfSize: node.size)
node.physicsBody!.categoryBitMask = fallingNodeCategory
node.physicsBody!.contactTestBitMask = baseNumberCategory
node.physicsBody!.collisionBitMask = 0
node.physicsBody!.allowsRotation = false
node.physicsBody!.velocity = nodeVelocity
}
func didBeginContact(contact: SKPhysicsContact) {
if isContactBetween(fallingNodeCategory, and: baseNumberCategory, contact: contact) {
handleContactBetweenFallingNodeAndBaseNumber(contact)
} else {
print("\nUNKNOWN CONTACT OCCURED\n")
}
updateInternalState()
checkGameOverCondition()
}
required init?(coder aDecoder: NSCoder) {
// gameZone = aDecoder.decodeObjectForKey(gameZoneKey) as! GameZone
super.init(coder: aDecoder)
gameZone = self.children[0] as! GameZone //Not decoded by itself but somehow decoded with the this GameScene Object (the "self" object here)
gameZone.delegate = self
self.physicsWorld.contactDelegate = gameZone
}
override func encodeWithCoder(aCoder: NSCoder) {
super.encodeWithCoder(aCoder)
aCoder.encodeObject(gameZone, forKey: gameZoneKey) //Gets coded
}
I was able to figure out my own problem here. A fundamental understanding I was lacking is the SKNodes encode their own child nodes.
// gameZone = aDecoder.decodeObjectForKey(gameZoneKey) as! GameZone
The gameZone object is a child node. So I was encoding it as well as other key objects twice which was leading to my problem. The problem was had nothing to do with the physics world contact delegate or encoding/decoding.