Fast-paced SpriteKit game has irregular CPU activity and is jittery/lags despite frame rate staying high - Swift - swift

I'm having the issue on a simple but fast-paced SpriteKit game, but I've reduced my code just to a bouncing ball and still get the issue to a lesser extent:
override func didMove(to view: SKView) {
super.didMove(to: view)
physicsWorld.contactDelegate = self
physicsWorld.speed = 1
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
borderBody.contactTestBitMask = BallCategory
addBall()
}
func addBall() {
let size = CGSize(width: 20, height: 20)
let position = CGPoint(x: frame.width / 2, y: 50)
let texture = SKTexture(image: #imageLiteral(resourceName: "whiteCircle"))
let ball = SKSpriteNode(texture: texture, size: size)
ball.position = position
ball.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
ball.fillColor = .white
ball.lineWidth = 0
addStandardProperties(node: ball, name: "ball", z: 5, contactTest: 0, category: BallCategory)
ball.physicsBody?.isDynamic = true
addChild(ball)
launchBall()
}
func addStandardProperties(node: SKNode, name: String, z: CGFloat, contactTest: UInt32, category: UInt32) {
node.name = name
node.zPosition = z
node.physicsBody?.isDynamic = false
node.physicsBody?.affectedByGravity = false
node.physicsBody?.mass = 0
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0
node.physicsBody?.linearDamping = 0
node.physicsBody?.angularDamping = 0
node.physicsBody?.angularVelocity = 0
node.physicsBody?.contactTestBitMask = contactTest
node.physicsBody?.categoryBitMask = category
}
func launchBall() {
let ball = childNode(withName: "ball")!
ball.physicsBody?.velocity = CGVector(dx: 0, dy: 500)
}
This code results in a ball (SKSpriteNode) bouncing up and down. When I run this, CPU usage starts at around 10% on my iPhone 6s and then after increases to around 25-30% after maybe 30-60 seconds (no idea why it's increasing). Throughout all of this, the frame rate stays very close to 60 FPS, usually going no lower than 58 FPS (it's the same way when I run the full game).
Almost any time an alert pops up (e.g., text messages, logging into Game Center, etc.), the lag shows up and shows up at random times when I'm running the full game.
I've also tried deleting and re-running the app, cleaning the project, deleting derived data and running in Release mode. None of these worked permanently.
Should I give up on SpriteKit and try another framework? If so, which? Cocos2D?
Any help is appreciated.

This is the result of Apple prioritising system calls over just about everything else.
When the system wants to know something, check something or otherwise do its thing it does so at the mercy of everything else.
No other engine will be able to help with this, there's no way to silence the system's constant activities through code.
You can get a slight improvement by putting on Flight Mode and turning off WIFI and Bluetooth. The system seems to be somewhat aware that it's in a quieter mode and does less because it's got no 4G or other connectivity it can go communicating with.
Further, there's been some pretty big changes to palm rejection in iOS 11 that's played havoc with the first round of iPad Pro models and creative software, creating multi-second rejection of all touch input. When this kind of thing can make it through to a GM you can be pretty sure they're slipping other messiness through.
Here's some complaints about iOS 11 performance: https://www.macrumors.com/2017/09/25/ios-11-app-slowdowns-performance-issues/

Turns out I had 2 SKViews in my view controller. By default, when you start a project as a SpriteKit game, Xcode sets the view controller root/superview of the GameViewController as an SKView. At some point, I had added a second SKView because I didn't intend for the scene to take up the entire screen and I apparently didn't realize that the VC root view was still set as an SKView. So every time GameViewController loaded, it was loading two SKViews, which is why I saw 120 FPS in Xcode.
I fixed the issue by simply removing the SKView class designation from the VC root view.

Related

Swift - Sprite Kit floating bubbles stuck to corners

I'm trying to create a floating bubble view on my Watch App. The bubbles can collide & bounce off each other & the sides of the screen. But for some reason the bubbles are appearing out of the view bounds & getting stuck on the sides of the frame instead of bouncing off. This code works as expect on my iOS application but when using the same code in my Watch app, it doesn't.
It doesn't make much sense to me that this exact code works perfectly on my iOS app but not on the Watch App.
I'm passing the below code into a SpriteView in my SwiftUI View
let ballCategory: UInt32 = 0xb0001
let edgeCategory: UInt32 = 0xb0010
var nodeCount = 0
override func sceneDidLoad() {
//set physicsWorld properties
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
//set edges as PhysicsBody
let edge = SKPhysicsBody(edgeLoopFrom: self.frame)
edge.friction = 0
edge.categoryBitMask = edgeCategory
self.physicsBody = edge
makebubble()
makebubble()
makebubble()
makebubble()
}
func makebubble() {
let bubbleTexture = SKTexture(imageNamed: "bubble")
let bubble = SKSpriteNode(texture: bubbleTexture)
let bphysicsBody = SKPhysicsBody(circleOfRadius: bubbleTexture.size().height/2)
bphysicsBody.isDynamic = true
bphysicsBody.usesPreciseCollisionDetection = true
bphysicsBody.restitution = 0.5
bphysicsBody.friction = 0
bphysicsBody.angularDamping = 0
bphysicsBody.linearDamping = 0
bphysicsBody.categoryBitMask = ballCategory
bphysicsBody.collisionBitMask = ballCategory | edgeCategory
bphysicsBody.contactTestBitMask = ballCategory | edgeCategory
bubble.physicsBody = bphysicsBody
bubble.name = "bubble"
// Get a random possition within the width of the scene
let x = CGFloat(randomize(number: Int(size.width - 40)))
let y = CGFloat(randomize(number: Int(size.height - 40)))
// position the bubble
bubble.position.x = x
bubble.position.y = y
// Add the bubble
addMyChild(node: bubble)
}
func addMyChild(node:SKSpriteNode){
self.addChild(node)
node.physicsBody!.applyImpulse(CGVector(dx: 10.0, dy: -2.0))
nodeCount += 1
}
// function that returns a random int from 0 to n-1
func randomize(number: Int) -> Int{
return Int(arc4random()) % number
}
This has nothing to to with watchOS and everything to do with the small screen size of Apple Watches. Try running your code on iOS with a frame modifier of width 150 and height 150 and you'll see what I mean; the bubbles will likely stick to the side.
Your bubbles look like they stick to the edges because they slow down over time (due to restitution being 0.5 instead of 1) and it's statistically more probable for a bubble to have its final movement close to the screen edge (since they will eventually move to the edge, bounce off from it, thus slowing down and eventually halting).
Here are 3 things you can do about this:
as mentioned, increase restitution to 1 (this is optional, as it won't solve the "sticking to the edge" problem on its own, but it helps making the slowing down issue better)
detect when the bubbles stop (you can do this by checking the x and y velocity in the update(_:) function of your SKScene) and make a force that moves them slightly in a random direction. If you are in a fancy mood, you can even make a timer to make random, barely noticable forces that act like small air movements (chances are, it will even make the animation a bit more realistic)
create an outside bounding box with slightly non-linear/circular borders and corners to make the bubbles bounce off the walls in a different way

Sprite Kit Animations and Texture Atlases in Swift 4

Currently working on an application that requires a little bit of animations through an array of images. I've done tons of research online on how to resolve my issue but couldn't run into anything for Swift 4. Below is my code, it shows the first picture in the loop when I run the app but no animation at all. Everything looks fine, I don't see a problem with my code but maybe you guys can help. Appreciate it in advanced!
let atlas = SKTextureAtlas(named: “mypic”)
var TextureArray = [SKTexture]()
var person = SKSpriteNode()
override func didMove(to view: SKView) {
person = SKSpriteNode(imageNamed: "red_1.png")
person.size = CGSize(width: 150, height: 129)
person.position = CGPoint(x: 0, y: 0)
person = SKSpriteNode(imageNamed: atlas.textureNames[0])
for i in 1...atlas.textureNames.count {
let Name = "red_\(i).png"
TextureArray.append(SKTexture(imageNamed: Name))
}
self.addChild(person)
}
override func update(_ currentTime: TimeInterval) {
let myAnimation = SKAction.animate(with: TextureArray, timePerFrame: 0.1)
person.run(SKAction.repeatForever(myAnimation))
}
The animation action is placed in update, which is executed once every frame. So if the game runs at 60 FPS, update gets called 60 times in one second. This means that every second person gets 60 new myAnimation actions that it needs to run.
To fix this, consider placing the animation action somewhere else in your code, e.g. right after adding the person to the scene. Since this is a repeatForever action, the animation will run as you intended until either the action is removed from the node, or the node is removed from the scene.
Hope this helps!

Why are the positions of my nodes different on every device?

Ok, so I've been trying to position a line on my iPhone 6s+ and my iPad 2. The line was added in an SKCamera (I don't know if that effects it or not). Here is the code for the line:
var leftWall = SKSpriteNode()
leftWall.size = CGSize(width: 1, height: 10000)
leftWall.position = CGPoint(x: 0, y: 0)
leftWall.color = UIColor.red
leftWall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: leftWall.size.width, height: leftWall.size.height))
leftWall.physicsBody?.affectedByGravity = false
leftWall.physicsBody?.isDynamic = false
leftWall.physicsBody?.categoryBitMask = groundGroup
leftWall.physicsBody?.collisionBitMask = ballGroup
leftWall.physicsBody?.contactTestBitMask = 0
theCamera.addChild(leftWall)
Okay, so the x position is 0,0 right? Here is how it looks on my iPhone 6s+:
As you see, the line is in the middle?
Here is how it looks on my iPad 2:
I just don't understand why the position of the line is completely different.. Please help me!
You are not taking into account the cropping that happens on the iPad, or your scaleMode is all wrong
What you need to do is set all nodes anchor points (Including the scene) to (0.5,0.5), make sure your GameScene has a static size and not based on view.frame (750,1334 is a good one) and that your scale mode is set to .aspectFill. This makes (0,0) the center of the screen, and you will notice everything line up on all devices

How to scale to be at the same distance in all devices?

I'm having problems opening my game in different divices , in the 6s iphone plus looks much bigger the circle the center, also the small circle that is on the line changes position , I would like that the center circle was the same size and that the small circle always this half on the line.
import SpriteKit
struct Circle {
var position:CGPoint
var radius:CGFloat
}
class GameScene: SKScene {
let node = SKNode()
let sprite = SKShapeNode(circleOfRadius: 6)
var rotation:CGFloat = CGFloat(M_PI)
var circles:[Circle] = []
var circuloFondo = SKSpriteNode()
var orbita = SKSpriteNode()
let padding2:CGFloat = 26.0
let padding3:CGFloat = 33.5
let padding5:CGFloat = 285.5
var circulo = SKSpriteNode()
override func didMoveToView(view: SKView) {
scaleMode = .ResizeFill
backgroundColor = UIColor(red: 0.3, green: 0.65, blue: 0.9, alpha: 1)
orbita = SKSpriteNode(imageNamed: "orbita2")
orbita.size = CGSize(width:view.frame.size.width - padding2 , height: view.frame.size.width - padding2)
orbita.color = UIColor.whiteColor()
orbita.colorBlendFactor = 1
orbita.alpha = 1
orbita.position = view.center
self.addChild(orbita)
orbita.zPosition = 3
circuloFondo = SKSpriteNode(imageNamed: "circuloFondo")
circuloFondo.size = CGSize(width:view.frame.size.width - padding5 , height: view.frame.size.width - padding5)
circuloFondo.color = UIColor.whiteColor()
circuloFondo.alpha = 1
circuloFondo.position = view.center
self.addChild(circuloFondo)
circuloFondo.zPosition = 0
let radius1:CGFloat = (view.frame.size.width - padding3)/2 - 1
let radius2:CGFloat = (view.frame.size.width - padding5)/2 + 6.5
circles.append(Circle(position: view.center, radius: radius1))
circles.append(Circle(position: view.center, radius: radius2))
addChild(node)
node.addChild(sprite)
if let circle = nextCircle() {
node.position = circle.position
sprite.fillColor = SKColor.whiteColor()
sprite.zPosition = 4.0
sprite.position = CGPoint(x:circle.radius, y:0)
rotate()
}
You can get the width of the screen like this:
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
and then set elements in your UI to be a proportion of the screenWidth. For instance:
let radius1:CGFloat = screenWidth/4
//this would always give you a radius that is one quarter of the screen width
I've used this method a few times with success, hope it works for you.
Your main problem is your scene mode is set to ResizeFill. This will cause you so many headaches as you will have to do all the scaling yourself, hence the circles are different sizes on different devices. Having scale mode resizeFill will also affect things such as the physics engine or fontSizes which you will need to adjust for on every device.
I would recommend you use scene scale mode .AspectFill with the default scene size of 1024*768 (landscape) or 768*1024 (portrait). This is the same as the xCode default game template.
This way everything will look exactly the same on all iPhones. On iPads there will be slightly more screen space at the top and bottom which you simply cover with your background. The main trick is that you position your stuff from the center.
Furthermore you can use the universal assets in the asset catalogue and everything will look great and not blurry.
The only thing that you might have to adjust for this way is that on iPads you might need to move some buttons up/down if you want them on the top/bottom edge.
I strongly recommend you consider this as I can talk from experience that using scale mode ResizeFill is really bad. I have been through this pain with 2 games before I rewrote them because they were so inconsistent on all devices causing me so many bugs in the process. Lets not talk about the time I wasted testing on all devices, adjusting values until it felt right.
Hope this helps.

Unable to resize sprite kit image sizes

I am developing a sprite kit game and I am having a lot of trouble sizing by sprites. I used the following code to add the sprite to the scene
import SpriteKit
class GameScene: SKScene {
let hero = SKSpriteNode(imageNamed: "hero.png")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
hero.position = CGPointMake(self.size.width / 2, self.size.height / 5)
_ = NSTimer.scheduledTimerWithTimeInterval(0.6, target: self,
selector: Selector ("spawnBullets"), userInfo: nil, repeats: true)
self.addChild(hero)
}
func spawnBullets(){
let bullet = SKSpriteNode(imageNamed: "bullet.png")
bullet.zPosition = -5
bullet.position = CGPointMake(hero.position.x, hero.position.y);
let action = SKAction.moveToY(self.size.height + 30, duration: 0.6)
bullet.runAction(SKAction.repeatActionForever(action))
self.addChild(bullet)
However, every time I run my app in the simulator my hero takes up almost 80% of the screen. I tried resizing my image in photoshop (as well as resized my image in Xcode by using a different CGSize or a CGRect code) but then the image is displayed as a red X and my app crashes saying insufficient memory to process the images or something like that.
Then, I reduced the amount of pixels to 25x25 pixels thinking that it would take less processing memory but no, the images are still displayed as red x's or my app crashes.
So, either the sprites take up 80% of the screen or they don't load or alas the app crashes.