Swift game lags when creating spritenodes - ios

So I have recently uploaded a game to the app store and have been users reporting lags on older devices.
First of all, the game:
It is called "CURVE" (I am not sure if I am allowed to post its name here, but it would help you understand the problem).The idea of the game is that a ball moves up the screen and passes in gaps between walls. Also there are smaller balls that fall down from the main one, thus creating some sort of path.
Now, the lags. They occur in two main moments. First, when the path is created and second, when the player gets through the gaps.
When the player passes through the gaps, he collides with an invisible node. The collsion is noted, the score is updated. The node is then removed and the player proceeds.
I believe the lags occur when swift either creates a SpriteNode or deletes one. Any ideas as to how to deal with this problem?
Here is the code I use when spawning my obstacles - I think it is where the problem is
func createRocks() {
rockHeight = (frame.height * (heightOfRocks))
if gameState != .Dead {
switch spawnCount {
case 10:
levelUp()
case 20:
levelUp()
case 30:
levelUp()
case 40:
levelUp()
case 50:
levelUp()
case 60:
levelUp()
case 70:
levelUp()
case 80:
levelUp()
case 90:
levelUp()
case 100:
levelUp()
case 110:
levelUp()
case 120:
levelUp()
case 130:
levelUp()
case 140:
levelUp()
case 150:
levelUp()
default:
break
}
}
++spawnCount
let leftRock = SKShapeNode(rectOfSize: CGSize(width: (frame.width), height: (rockHeight)), cornerRadius: (cornerRad))
leftRock.fillColor = themeColor
leftRock.strokeColor = themeBorderColor
leftRock.lineWidth = themeBorderWidth
leftRock.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: ((frame.width) + (themeBorderWidth)), height: ((rockHeight) + (themeBorderWidth))))
leftRock.physicsBody?.dynamic = false
let rightRock = SKShapeNode(rectOfSize: CGSize(width: (frame.width), height: (rockHeight)), cornerRadius: (cornerRad))
rightRock.fillColor = themeColor
rightRock.strokeColor = themeBorderColor
rightRock.lineWidth = (themeBorderWidth)
rightRock.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: ((frame.width) + (themeBorderWidth)), height: ((rockHeight) + (themeBorderWidth))))
rightRock.physicsBody?.dynamic = false
leftRock.zPosition = CGFloat(10 + (21 * levelNumber))
rightRock.zPosition = CGFloat(10 + (21 * levelNumber))
// 2
let rockCollision = SKSpriteNode(color: UIColor.clearColor(), size: CGSize(width: (frame.width * 2), height: 32))
rockCollision.physicsBody = SKPhysicsBody(rectangleOfSize: rockCollision.size)
rockCollision.physicsBody?.dynamic = false
rockCollision.name = "scoreDetect"
addChild(leftRock)
addChild(rightRock)
addChild(rockCollision)
// 3
let rockWidth = frame.width
let yPosition = frame.height + leftRock.frame.height
let rockDistance = frame.width * (gapWidth)
let min = Int(((frame.width) * (wallWidth)) - (rockWidth / 2))
let max = Int(((frame.width) * (1.00 - ((wallWidth)))) - rockDistance - (rockWidth / 2))
let rand = min + Int(arc4random_uniform(UInt32(max - min)))
//GKRandomDistribution(lowestValue: min, highestValue: max)
let xPosition = CGFloat(rand)
// 4
leftRock.position = CGPoint(x: xPosition, y: yPosition)
rightRock.position = CGPoint(x: xPosition + rockWidth + rockDistance, y: yPosition)
rockCollision.position = CGPoint(x: xPosition, y: CGRectGetMidY(frame))
rockCollision.position = CGPoint(x: CGRectGetMidX(frame), y: yPosition + (rockCollision.size.height * 2))
let endPosition = frame.height + (leftRock.frame.height * 2)
let moveAction = SKAction.moveByX(0, y: -endPosition, duration: timeOfRockMovement)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
leftRock.runAction(moveSequence)
rightRock.runAction(moveSequence)
rockCollision.runAction(moveSequence)
}
func initRocks() {
let create = SKAction.runBlock { [unowned self] in
self.createRocks()
}
let wait = SKAction.waitForDuration(rockSpawnWait)
let sequence = SKAction.sequence([create, wait])
let repeatForever = SKAction.repeatActionForever(sequence)
runAction(repeatForever)
}

Your approach sounds very similar to a SpriteKit tutorial I wrote, but I had no lag at all there. SpriteKit is awfully fast, but it's possible your exact conditions are causing problems. Things to check:
1) Are you using texture atlases? These are faster to load than separate images in your bundle.
2) If you're re-using images a lot, can you store them in an SKTexture then load your nodes from there?
3) Are there other things happening, such as sounds playing? The iOS Simulator frequently has a brief lag when it plays its first sound in a game.
4) Is the path that gets created very complicated?

Related

SKLightNode causes crash if added too soon with SKSpriteNode

I have a scene with 20ish SKSpriteNodes that are added at run time. I also Add an SKLightNode
However I get a crash with the following log
-[MTLDebugRenderCommandEncoder validateDrawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:function:]:5605: failed assertion `Draw Indexed Primitives Validation indexBufferOffset(12) + (indexCount(222) * 2) must be <= [indexBuffer length](240). ' -[MTLDebugRenderCommandEncoder validateDrawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:function:]:5605: failed assertion `Draw Indexed Primitives Validation indexBufferOffset(12) + (indexCount(222) * 2) must be <= [indexBuffer length](240).
If I wait 10 seconds to add the SKLightNode the crash doesnt happen, also if I disable shadows on the SKSpriteNode the crash doesnt happen.
Fyi this was an old swift 2 project that worked fine, I manually upgraded it to swift 4 and these issues started, any ideas?
the SKLightNode SKSpriteNodes are as follows
let light = SKLightNode();
light.categoryBitMask = 1;
light.falloff = 1;
light.ambientColor = .white
light.lightColor = .white
light.shadowColor = .black
let tree1 = SKSpriteNode(texture: SKTexture(imageNamed: "tree1.png"), color: grayColor, size: CGSize(width: 120, height: 270));
tree1.color = grayColor;
tree1.colorBlendFactor = 1;
tree1.position = CGPoint(x: (self.frame.size.width/2) - 170, y: 730)
tree1.shadowCastBitMask = 1;
tree1.shadowedBitMask = 1;
tree1.name = String(format: "%f", tree1.position.y)

Draw two or more markers at same time (library Charts)

How can I display two or more markers on a graph at the same time?
This is part of my code for this task
chartView.data = LineChartData(dataSets: [dataSet, dataSetForecast, dataSetUpper, dataSetLower])
let upperHigh = Highlight(x: (dataSetUpper.xMax + dataSetUpper.xMin) / 2,
y: dataSetUpper.yMax, dataSetIndex: 2)
upperHigh.dataIndex = 2
let lowerHigh = Highlight(x: (dataSetLower.xMax + dataSetLower.xMin) / 2,
y: dataSetLower.yMax, dataSetIndex: 3)
lowerHigh.dataIndex = 3
let forecastHigh = Highlight(x: (dataSetForecast.xMax + dataSetForecast.xMin) / 2,
y: dataSetForecast.yMax, dataSetIndex: 1)
forecastHigh.dataIndex = 1
chartView.highlightValues([upperHigh, lowerHigh, forecastHigh])
chartView.drawMarkers = true
chartView.data?.highlightEnabled = true
chartView.notifyDataSetChanged()
chartView.setVisibleXRangeMaximum(3600 * 3)
chartView.setVisibleXRangeMinimum(0)
Library https://github.com/danielgindi/Charts

In SwiftUI how can I animate with an offset in both x and y without causing a compiler error

I am having trouble animating with an offset in both x and y at the same time and want to know how to change things so I get a successful build.
I can either build successfully as shown (with y animating correctly and x using 0) or I can swap the commenting around and have x animating correctly with y using 0 or 0.
If I change the commenting to allow both x and y to animate (neither using 0 as the first part of the expression), the compiler fails with the dreaded "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions".
I've tried refactoring in lots of different ways and the way I've set and used the constants a and i are the simplest I can manage (and I've tried many different ways!)
ZStack {
let numSlots = kidsMaxSlots
let gwidth = geometry.size.width
let gameplayArea = geometry.size.height * gwidth
let slotsArea = gameplayArea / CGFloat(numSlots)
let slotSize = slotsArea.squareRoot()
let widthInSlots = Int(geometry.size.width / slotSize + 0.5)
let newSlotSize = geometry.size.width / CGFloat(widthInSlots)
let a = Int(geometry.size.width / (geometry.size.width / CGFloat(Int(geometry.size.width / (geometry.size.height * geometry.size.width / CGFloat(numSlots)).squareRoot() + 0.5))))
let i = (geometry.size.width / (geometry.size.height * geometry.size.width / CGFloat(numSlots)).squareRoot()).rounded()
let myShift = newSlotSize / 2
ForEach(gameplayDisplayArea.slotsArray.indices){ mySequence in
gameplayDisplayArea.slotsArray[gameplayDisplayArea.slotsArray[mySequence].id].slotImage
.resizable()
.frame(width: newSlotSize, height: newSlotSize)
.background(Color.green.opacity(0.4))
.position(
x: myShift + CGFloat(mySequence % a) * newSlotSize,
y: myShift + CGFloat(mySequence / widthInSlots) * newSlotSize
)
.offset(x: self.myAnimDefs.startAnimation ?
// geometry.size.width / 2 - myShift - CGFloat(mySequence % a) * newSlotSize
0
: 0,
y: self.myAnimDefs.startAnimation ?
geometry.size.height - CGFloat(floor(CGFloat(mySequence) / i)) * newSlotSize
// 0
: 0)
}
Ack!
I lost sight of what I was trying to achieve and got hung up on trying to achieve it that way. It would still be good to know how to get the compiler to build as per my original question.
I now realize that I can achive what I wanted by doing it all with .position like this:
.position(x: self.myAnimDefs.startAnimation ?
geometry.size.width / 2
: myShift + CGFloat(mySequence % a) * newSlotSize,
y: self.myAnimDefs.startAnimation ?
geometry.size.height + myShift
: myShift + CGFloat(mySequence / widthInSlots) * newSlotSize
)

How to show two items in one row? with using uicollectionView

I followed this video for study Collection View (https://www.youtube.com/watch?v=jQ8EUsQZJ5g)
And I wrote code like this :
if ( CollectionViewFlowLayout == nil ){
let numberOfItemsForRow : CGFloat = 2
let UpDownSpace : CGFloat = 5
let LeftRightSpace : CGFloat = 5
//CollectionViewFlowLayout은 delgate 처럼 소재지를 수정하고 꾸밀 수 있는 것이다.
CollectionViewFlowLayout = UICollectionViewFlowLayout()
//이건 margin의 크기를 정해주는 것이다.
CollectionViewFlowLayout?.sectionInset = UIEdgeInsets.zero
//이건 화면을 수직으로 내릴 것이냐, 수평으로 스크롤 할것이냐를 정해준다.
CollectionViewFlowLayout?.scrollDirection = .vertical
//소재지의 위 아래의 간격을 정해준다.
CollectionViewFlowLayout?.minimumLineSpacing = UpDownSpace
//같은 줄에 있는 소재지의 오른쪽 왼쪽 사이의 간격을 정해준다.
CollectionViewFlowLayout?.minimumInteritemSpacing = LeftRightSpace
let total = (LeftRightSpace * (numberOfItemsForRow - 1))
let width = (CollectionView.frame.width - total) / numberOfItemsForRow
let height = width
//itemsize : 소재지의 크기를 정 할 수 있다
CollectionViewFlowLayout?.itemSize = CGSize(width: width, height: height)
CollectionView.setCollectionViewLayout(CollectionViewFlowLayout!, animated: true)
}
But when I use simulator with iPhone 8, It make error
Other things are Good! without iPhone 8
enter image description here
I think your total should be calculated as:
let total = LeftRightSpace * (numberOfItemsForRow + 1), because you are taking away left, right and interitem spacing.
Or try calculating width as:
let width = (UIScreen.main.bounds.width - total) / numberOfItemsForRow

index out of range array error when removing an element in swift

So I have a platform that spawns a mine and coins on it so first i spawn the mine using a bunch of spawn locations created basd on the size of the screen width and player like so:
var spawnLocations:[CGFloat] = []
func getObjectSpawnLocation() {
spawnLocations.removeAll()
//Create 5 possible spawn locations
let numberOfNodes = 5
// Spacing between nodes will change if: 1) number of nodes is changed, 2) screen width is changed, 3) node's size is changed.
for i in 0...numberOfNodes - 1 {
// spacing used to space out the nodes according to frame (which changes with screen width) (player width go up for closer nodes down for bigger space)
var xPosition = (frame.maxX + thePlayer.size.width / 0.57) / CGFloat((numberOfNodes - 1)) * CGFloat(i)
//add a half of a player's width because node's anchor point is (0.5, 0.5) by default
xPosition += thePlayer.size.width/2 //2
//I have no idea what this does but it works so fuck it.
xPosition -= frame.maxX/1 //1.6
spawnLocations.append( xPosition )
}
}
func spawnMine() {
if (theType == LevelType.normal && imageName == "WallObjectFarLeft") {
for _ in 0...0 {
getObjectSpawnLocation()
let obstacle:Object = Object()
obstacle.theType = LevelType.normal
obstacle.createObject()
obstacle.zPosition = 100
addChild(obstacle)
let randx = spawnLocations[0]
obstacle.position = CGPoint(x: randx, y: 0)
spawnLocations.remove(at: 0)
createSpecialObject()
}
}
then at the end of the spawn mine function I remove the mines location from the array so it can't be picked again and then I run the create special object function which creates the coin object on the platform like so:
func createSpecialObject() {
print("\(spawnLocations.count)")
for i in 0..<spawnLocations.count {
let diceRoll = arc4random_uniform(7) + 1
if (diceRoll <= 2) {
let specialObstacle:Object = Object()
specialObstacle.theType = LevelType.normal
specialObstacle.createSpecialObject()
specialObstacle.zPosition = 100
addChild(specialObstacle)
var randx = spawnLocations[i]
specialObstacle.position = CGPoint(x: randx, y: 0)
}
}
}
All this works fine when only removing one of the potential spawn locations from the array but when I try and remove two or even three locations from the array that have more than one mine on a platform and run the game it crashes and gives me a fatal error saying index out of range. What am I doing wrong here and how can I fix this? I remove them like so if there are more than one mine on a platform
else if (theType == LevelType.normal && imageName == "WallObjectFarLeft&Middle&FarRight") {
for num2 in 0 ..< 3 {
getObjectSpawnLocation()
let obstacle:Object = Object()
obstacle.theType = LevelType.normal
obstacle.createObject()
obstacle.zPosition = 100
addChild(obstacle)
var randx = spawnLocations[0]
if num2 == 1 {
randx = spawnLocations[2]
}
if num2 == 2 {
randx = spawnLocations[4]
}
obstacle.position = CGPoint(x: randx, y: 0)
}
spawnLocations.remove(at: 0)
spawnLocations.remove(at: 2)
spawnLocations.remove(at: 4)
createSpecialObject()
}
Here I am giving you example what actually what you'r trying to do in you below code work
spawnLocations.remove(at: 0)
spawnLocations.remove(at: 2)
spawnLocations.remove(at: 4)
In your project, your array have four values
Index - > 0, 1, 2, 3
Now, you remove object at index : 0, -> spawnLocations.remove(at: 0)
So now your updated array have 3 values,
index -> 0, 1, 2
In next step, you remove index : 2 -> spawnLocations.remove(at: 2)
So updated array will be,
index -> 0, 1
Now in the same array you have 2 values at index 0 & 1 and your are tring to remove object at index : 4 -> spawnLocations.remove(at: 4)
Hope you get your mistake.
Solution
Reverse you order of removing array object
spawnLocations.remove(at: 4)
spawnLocations.remove(at: 2)
spawnLocations.remove(at: 0)
init: [0,1,2,3,4]
remove at index 0
[1,2,3,4]
remove at index 2
[1,2,4]
remove at index 4
index out of range
You need to shift yours index after each remove
init: [0,1,2,3,4]
remove at index 0
[1,2,3,4]
remove at index 2 - 1
[1,3,4]
remove at index 4 - 2
[1,3]

Resources