How to lower Renderer Utilization? - ios

I am having sever performance issues with a SceneKit game. Total vertices count 4000. Renderer Utilization is 100% on ipad3, framerate 16fps, even though Tiler utilization is only about 8%. This is screenshot from OPENGL ES Analysis.
This is the entire code that is used to specify what the object should look like. As you can see there is no custom shaders. Just a box that falls on the floor because of physics applied to it. Textures and 2 lights are applied in the .scn file. Everything is simple, basic but the performance is terrible. How do I lower Renderer Utilization on the device? I am guessing some default settings that sceneKit uses aren't suitable for my app and cause severe performance problems. Which one should I double check?
//called in viewDidLoad:
func setupScene(){
scene = SCNScene(named: "GameScene.scn")
sceneView.scene = scene
sceneView.delegate = self
scene.physicsWorld.contactDelegate = self
scene.physicsWorld.gravity = SCNVector3Make(0, 0, 0)
scene.physicsWorld.speed = 1.8
sceneView.jitteringEnabled = true
sceneView.autoenablesDefaultLighting = false
sceneView.antialiasingMode = SCNAntialiasingMode.None
let valueVector: NSValue = NSValue(SCNVector3: SCNVector3Make(0.7, 0.7, 0.7))
objectNode = scene.rootNode.childNodeWithName("object", recursively: true)!
let physicsShape = SCNPhysicsShape(node: objectNode, options: [SCNPhysicsShapeTypeKey:SCNPhysicsShapeTypeBoundingBox, SCNPhysicsShapeScaleKey:valueVector])
objectNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Dynamic, shape: physicsShape)
let floorNode = scene.rootNode.childNodeWithName("floor", recursively: true)
floorNode?.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static, shape: nil)
objectNode.categoryBitMask = 1
floorNode?.categoryBitMask = 1
objectNode.physicsBody?.categoryBitMask = 1
floorNode?.physicsBody?.categoryBitMask = 1
objectNode.physicsBody?.collisionBitMask = 1
floorNode?.physicsBody?.collisionBitMask = 1
floorNode?.physicsBody?.restitution = 0.7
objectNode.physicsBody?.restitution = 0.7
}
func speed(velocity:SCNVector3) -> Float
{
let dx = Float(velocity.x)
let dy = Float(velocity.y)
let dz = Float(velocity.z)
return sqrtf(dx*dx+dy*dy+dz*dz)
}
func angularSpeed(angularSpeed: SCNVector4)->Float{
let x = angularSpeed.x
let y = angularSpeed.y
let z = angularSpeed.z
return sqrtf(x*x+y*y+z*z)
}
func nearlyAtRest(node:SCNNode) -> Bool
{
return speed((node.physicsBody?.velocity)!) < 0.05 && ( self.resting == false) && (angularSpeed((node.physicsBody?.angularVelocity)!) < 1)
}
// PHYSICS CONTACT DELEGATE DELEGATE METHOD
func renderer(renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: NSTimeInterval) {
if nearlyAtRest(objectNode) && speed((objectNode.physicsBody?.velocity)!) != 0 {
print("it stopped")
}

Related

Create a flickering/variable SKLightNode in SpriteKit - simulate campfire lighting

I have an animated campfire using an texture atlas in SpriteKit, I am trying to simulate the variable lighting that a fire would produce. I was able to achieve a flicker by varying the falloff by passing in a random number form 0...1.5. It works but is a little too crazy - looking for a suggestion on smoothing it out to be more subtle and realistic - maybe pass an array of set values thru - not sure how I would do that? Or some sort of easing?
func buildCampfire() {
let campfireAtlas = SKTextureAtlas(named: "Campfire")
var fireFrames: [SKTexture] = []
let numImages = campfireAtlas.textureNames.count
for i in 1...numImages {
let fireTextureName = "campfire\(i)"
fireFrames.append(campfireAtlas.textureNamed(fireTextureName))
}
animatedCampfire = fireFrames
let firstFrameTexture = animatedCampfire[0]
campfire = SKSpriteNode(texture: firstFrameTexture)
campfire.size.height = 300
campfire.size.width = 300
campfire.position = CGPoint(x: 108, y: -188)
addChild(campfire)
}
func animateCampfire() {
campfire.run(SKAction.repeatForever(SKAction.animate(with: animatedCampfire, timePerFrame: 0.1, resize: false, restore: true)), withKey: "campfireAnimated")
}
func flickerCampfire() {
if let campfireLight = self.childNode(withName: "//campfireLight") as? SKLightNode {
campfireLight.falloff = CGFloat.random(in: 0..<1.5)
} else {
print("cannot find light node")
}
}
override func update(_ currentTime: TimeInterval) {
flickerCampfire()
}
}

In SceneKit we can not create PhysicalBody at every location, is that normal?

At sceneView we have a big cube.
SCNBox(width: 10, height:10, length: 10, chamferRadius: 0)
We are placing spheres(radius = 1) on the six surfaces of that bigger cube.
Spheres have dynamic physicalBody.
Debugger mode is active with
sceneView.debugOptions = [.showPhysicsFields, .showPhysicsShapes]
When we run app we can see that only sphere with node position
SCNVector3(x:0, y: 0, z: 10.5)
seems to have body, only this one marked by debugger.
Also, only this sphere responds if a kinetic body touches it.
Is there something we are missing, why the spheres on other surfaces can not have physical body.
This is how I create spheres;
var ball:[SCNSphere] = []
var ballNode:[SCNSNode] = []
var body:[SCNPhysicsBody] = []
let pShape = SCNSphere()
pShape.radius = 1
pShape.segmentCount = 1
for k in 0..<6 {
ball.append(SCNSphere())
ballNode.append(SCNSphere())
ball[k].radius = 1
ball[k].hunt.firstMaterial?.diffuse.contents = UIColor.red
ballNode[k]= SCNNode(geometry:ball[k])
let shape = SCNPhysicsShape(geometry: pShape, options: nil)
body.append(SCNPhysicsBody())
body[k] = SCNPhysicsBody(type: .dynamic, shape: shape)
body[k].isAffectedByGravity = false
body[k].friction = 1
body[k].restitution = 0.1
body[k].angularDamping = 1
ballNode[k].physicsBody = body[k]
let coors = createCoors(index: k)
ballNode.position = SCNVector3(coors.x, coors.y, coors.z)
bigBoxNode.addChildNode(ballNode[k])
}

What is causing node to slow down despite velocity remaining constant?

I have a SCNSphere that is climbing a 45 degree hill.
The node maintains a consistent speed until the same point at every level, at which point it unexpectedly drops in speed, here is a 10 second clip of the issue.
The drop in speed occurs at 8 seconds in this clip.
When the node reaches a z position of -240, it seems as though the entire game speed is cut in half.
I have tested this in the following ways always without success.
Tried testing without gravity.
Tried testing without colliding with the hill.
Tried testing without damping or friction.
Tried printing the nodes velocity to notice any changes, although the velocity remains at -5.0 on the z axis for the duration of the
level despite the significant drop in speed.
Tried printing the physicsWorld speed to notice any changes, although the speed remains at 1.0 for the duration of the
level despite the significant drop in speed.
Checked for a drop in frame rate although it maintains a frame rate of 60 fps, making only 14 draw calls in total with a poly count under 15k.
The sphere's velocity is updated at every frame in the renderer using the following function.
func updatePositions() {
if let playerPhysicsBod = playerNode.physicsBody {
playerPhysicsBod.velocity.x = (lastXPosition - playerNode.position.x) * 8
playerPhysicsBod.velocity.z = -5
print("player velocity is \(playerNode.physicsBody!.velocity)")
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
updatePositions()
updateSegments()
}
This project is a fresh one, and so there aren't many lines, I will include the entire code base below.
class GameViewController: UIViewController {
let scene = SCNScene(named: "art.scnassets/gameScene.scn")!
let jump = SCNScene(named: "art.scnassets/jump.scn")!.rootNode.childNode(withName: "jump", recursively: true)!
let box = SCNScene(named: "art.scnassets/box.scn")!.rootNode.childNode(withName: "box", recursively: true)!
var playerNode = SCNNode()
var cameraNode = SCNNode()
var lastXPosition = Float()
var floorSegments = [SCNNode]()
override func viewDidLoad() {
super.viewDidLoad()
// retrieve the SCNView
let scnView = self.view as! SCNView
// scnView.isJitteringEnabled = true
scnView.scene = scene
scnView.delegate = self
scnView.showsStatistics = true
setupCamera()
setupPlayer()
setupSegments()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let tLocationX = touches.first?.location(in: self.view).x else { return }
let ratio = tLocationX / self.view.frame.maxX
lastXPosition = Float((5 * ratio) - 2.5)
}
func setupCamera() {
if let camera = scene.rootNode.childNode(withName: "camera", recursively: true) {
cameraNode = camera
cameraNode.name = "camera"
}
}
func setupPlayer() {
if let player = scene.rootNode.childNode(withName: "player", recursively: true) {
playerNode = player
playerNode.name = "player"
}
}
func setupSegments() {
if let segment = scene.rootNode.childNode(withName: "segment", recursively: true) {
floorSegments.append(segment)
}
}
}
extension GameViewController: SCNSceneRendererDelegate {
func updateSegments() {
playerNode.position = playerNode.presentation.position
if let lastSegmentClone = floorSegments.last?.clone() {
lastSegmentClone.childNodes.forEach { (node) in
node.removeFromParentNode()
}
if abs(playerNode.position.z - lastSegmentClone.position.z) < 30 {
// set up next segment
lastSegmentClone.position = SCNVector3(lastSegmentClone.position.x, lastSegmentClone.position.y + 4, lastSegmentClone.position.z - 4)
floorSegments.append(lastSegmentClone)
// Add falling blocks to the segment
for _ in 0...2 {
let boxClone = box.clone()
let randomX = Int.random(in: -2...2)
let randomY = Int.random(in: 1...3)
boxClone.eulerAngles.z = Float(GLKMathDegreesToRadians(-45))
boxClone.position = SCNVector3(randomX, randomY, -randomY)
lastSegmentClone.addChildNode(boxClone)
}
// Add falling blocks to the segment
for (index,segment) in floorSegments.enumerated().reversed() {
if segment.position.z > playerNode.position.z + 5 {
floorSegments.remove(at: index)
segment.childNodes.forEach { (node) in
node.removeFromParentNode()
}
segment.removeFromParentNode()
}
}
scene.rootNode.addChildNode(lastSegmentClone)
}
}
}
func updatePositions() {
if let playerPhysicsBod = playerNode.physicsBody {
playerPhysicsBod.velocity.x = (lastXPosition - playerNode.position.x) * 8
playerPhysicsBod.velocity.z = -5
print("player velocity is \(playerNode.physicsBody!.velocity)")
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
updatePositions()
updateSegments()
}
}
It is difficult to judge w/o seeing scn files.
I hope there is no collision between the player and floor nodes...
Player's Position is a float, It might be inexact comparison for equality. I think you should avoid player's position adjustment, as it should be almost the same:
playerNode.position = playerNode.presentation.position
Why don't you keep reference on an empty segment node?
In the method setupSegments clone the segment and after removing children nodes, keep it. No point to always remove children nodes:
lastSegmentClone.childNodes.forEach { (node) in
node.removeFromParentNode()
}
Plus I think you should "reverse" the order in updateSegments:
if let lastSegment = floorSegments.last { // no cloning here....
if abs(playerNode.position.z - lastSegment.position.z) < 30 {
let lastSegmentClone = lastSegment.clone() // better to use emptySegmentNode
/* If empty segment then it's not needed to remove children nodes...
lastSegmentClone.childNodes.forEach { (node) in
node.removeFromParentNode() */
Also if you're removing parent node, children nodes are going to be removed automatically....
// Avoid commented code
/*segment.childNodes.forEach { (node) in
node.removeFromParentNode()
}*/
segment.removeFromParentNode()
No sure, but perhaps for floor segments it is better to use custom action for removal:
// calculate somehow wait duration, based on the player's position, velocity or use pure constant..
let removeAction = SCNAction.sequence([SCNAction.waitForDuration(2.0), SCNAction.removeFromParent()])
lastSegmentClone.run(removeAction)
In such case you just need a reference on the last floor node, and empty floor node.

SpriteKit Collision Detection Not Working Properly

I have three nodes in my game-
The Player -
This is a boat that you drag around using a joystick.
let collisionPlayer : UInt32 = 0x1 << 1
let apple = SKSpriteNode(texture: texture)
apple.position = position
apple.physicsBody = SKPhysicsBody(circleOfRadius: apple.size.width / 2.0)
apple.physicsBody?.affectedByGravity = false
apple.physicsBody?.isDynamic = true
apple.setScale(0.1)
addChild(apple)
("apple" is the boat)
The Enemy Boat -
This is a CPU boat that randomly follows the player around.
let collisionNPC : UInt32 = 0x1 << 0
appleNPC.position = position
appleNPC.physicsBody = SKPhysicsBody(circleOfRadius: appleNPC.size.width / 2.0)
appleNPC.physicsBody?.isDynamic = true
appleNPC.physicsBody?.affectedByGravity = false
appleNPC.position = CGPoint(x: 600, y: 200)
appleNPC.setScale(0.1)
addChild(appleNPC)
The Bullet
(self-explanatory)
let collisionBullet : UInt32 = 0x1 << 2
(The following code is in TouchesEnded)
bullet?.name = "Bullet"
bullet?.position = (appleNode?.position)!
bullet?.setScale(0.05)
bullet?.zPosition = 1
bullet?.physicsBody = SKPhysicsBody(circleOfRadius: (bullet?.size.width)!/2)
bullet?.physicsBody?.isDynamic = true
bullet?.physicsBody?.affectedByGravity = false
bullet?.physicsBody?.usesPreciseCollisionDetection = true
To move the bullet I use the code:
// Your code with delay
if bulletNumbers > 0 {
let offset = CGPoint(x: touchLocation.x - (bullet?.position.x)! , y: touchLocation.y - (bullet?.position.y)!)
//Stops Bullet from shooting backwards
//Get the direction of where to shoot
let direction = offset
//Make it shoot far enough to be guaranteed off screen
let shootAmount = CGPoint(x: direction.x * 10, y: direction.y * 10)
//Add the shoot amount to the current position
let realDest = CGPoint(x: shootAmount.x + (bullet?.position.x)!, y: shootAmount.y + (bullet?.position.y)!)
//Create the actions
addChild(bullet!)
self.bulletNumbers -= 1
let distance = sqrt(pow(realDest.x - (appleNode?.position.x)!, 2) +
pow(realDest.y - (appleNode?.position.y)!, 2))
// run the sequence of actions for the firing
let duration = TimeInterval(distance / PlayerMissileSpeed)
let missileMoveAction = SKAction.move(to: realDest, duration: duration)
let when = DispatchTime.now() + 10 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) { self.bulletNumbers += 1
}
bullet?.run(missileMoveAction) {
self.bullet?.isHidden = true
self.bullet?.removeFromParent()
print("\(self.bulletNumbers)")
}}
else if bulletNumbers <= 0 {
print("reload")
let when = DispatchTime.now() + 2 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
}
}
}
func didBegin(_ contact: SKPhysicsContact) {
print("test")
}
func setRandomStickColor() {
let randomColor = UIColor.random()
moveAnalogStick.stick.color = randomColor
}
func setRandomSubstrateColor() {
let randomColor = UIColor.random()
moveAnalogStick.substrate.color = randomColor
}
override func update(_ currentTime: TimeInterval) {
/* Called before each frame is rendered */
super.update(currentTime)
let _:UInt32 = arc4random_uniform(1) //
appleNodeCpuSpeed = CGFloat(1)
var locationx : CGFloat
var locationy: CGFloat
locationx = (appleNode?.position.x)!
locationy = (appleNode?.position.y)!
let dx = locationx - (appleNodeNPC?.position.x)!
let dy = locationy - (appleNodeNPC?.position.y)!
let angle = atan2(dy, dx)
let vx = cos(angle) * appleNodeCpuSpeed
let vy = sin(angle) * appleNodeCpuSpeed
self.appleNodeNPC?.position.x += vx
self.appleNodeNPC?.position.y += vy
let when = DispatchTime.now() + 0.25 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
self.appleNodeNPC?.zRotation = angle + 90
}
//2
This works fine. The bullet is launched from the boat to the touch location, however, things start to go wrong when I tried to introduce collisions. Here is my code below:
appleNode?.physicsBody?.categoryBitMask = collisionPlayer
appleNode?.physicsBody?.collisionBitMask = collisionNPC
appleNode?.physicsBody?.contactTestBitMask = 0
appleNodeNPC?.physicsBody?.categoryBitMask = collisionNPC
appleNodeNPC?.physicsBody?.collisionBitMask = collisionPlayer
appleNodeNPC?.physicsBody?.contactTestBitMask = collisionBullet
bullet?.physicsBody?.categoryBitMask = collisionBullet
bullet?.physicsBody?.collisionBitMask = 0
bullet?.physicsBody?.contactTestBitMask = collisionNPC
physicsWorld.contactDelegate = self
view.showsPhysics = true
Then in my didBegin function I have:
func didBegin(_ contact: SKPhysicsContact) {
print("test")
}
Let's start from the beginning. I tap the screen and the bullet is launched. The bullet does not collide with the Player, which is what I originally wanted. However, when the bullet makes contact with the Enemy ship it DOES collide. It goes around the ship and keeps going on to its original destination. I want the bullet to contact the ship and nothing else - no collisions. I would hope to assume that my error here is because of my code for moving the bullet, but i'm not sure. Any help is appreciated.
Define unique categories, ensure your class is a SKPhysicsContactDelegate and make yourself the physics contact delegate:
//Physics categories
let appleCategory: UInt32 = 1 << 0
let enemyCategory: UInt32 = 1 << 1
let bulletCategory: UInt32 = 1 << 2
class GameScene: SKScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
Assign the categories (usually in didMove(to view:) :
apple.physicsBody.catgeoryBitMask = appleCategory
enemy.physicsBody.catgeoryBitMask = enemyCategory
bullet.physicsBody.catgeoryBitMask = bulletCategory
(Make sure you've created physics bodies for each node)
Set up collisions:
apple.physicsBody?.collisionBitMask = 0 // apple/player collides with nothing
enemy.physicsBody?.collisionBitMask = 0 // enemy collides with nothing
bullet.physicsBody?.collisionBitMask = 0 // bullet collides with nothing
or even:
for node in [apple, enemy, bullet] {
node.physicsBody?.collisionBitMask = 0 // collides with nothing
}
Set up contacts
bullet.physicsBody?.collisionBitMask = enemyCategory // bullet contacts enemy
Make sure that at least one of the objects involved in each potential contact has the isDynamic property on its physics body set to true, or no contact will be generated. It is not necessary for both of the objects to be dynamic.
You should now get didBegin called when the bullet and the enemy make contact. You could code didBegin like this:
func didBegin(_ contact: SKPhysicsContact) {
print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case bulletCategory | enemyCategory:
print("bullet and enemy have contacted.")
let bulletNode = contact.bodyA.categoryBitMask == bulletCategory ? contact.bodyA.node : contact.bodyB.node
enemyHealth -= 10
bulletNode.removeFromParent
default:
print("Some other contact occurred")
}
}

Apply amplitude modulation to pink noise operation

AudioKit provides documentation on creating white noise with panning as follows:
let generator = AKOperationGenerator { _ in
let white = AKOperation.whiteNoise()
let pink = AKOperation.pinkNoise()
let lfo = AKOperation.sineWave(frequency: 0.3)
let balance = lfo.scale(minimum: 0, maximum: 1)
let noise = mixer(white, pink, balance: balance)
return noise.pan(lfo)
}
However rather than panning, I'm looking to change the amplitude with the following parameters (from SoundForge Pro):
// AmplitudeModulation -> Sine
// 0.15 (s) -> Modulation frequency
// Minimum amplitude: Up to -30.0
// Stereo pan: Up to 20
// Dry out -30db
Is this possible using AudioKit?
You could use AKTremolo.
class ViewController: UIViewController {
let whiteNoise = AKWhiteNoise()
let tremolo = AKTremolo()
let mixer = AKMixer()
override func viewDidLoad() {
AudioKit.output = mixer
AudioKit.start()
whiteNoise >>> tremolo >>> mixer
tremolo.frequency = 0
whiteNoise.start()
let slider = AKSlider(property: "Tremolo") { value in
self.tremolo.frequency = 100 * value
}
slider.frame = CGRect(x: 0, y: 100, width: view.bounds.width, height: 100)
view.addSubview(slider)
}
}
You can do amplitude modulation by using AKOperationEffect. For example:
let Amplfo = AKOperation.sineWave(frequency: freq, amplitude: 1.0)
let Output = AKOperationEffect(generator) { generator, _ in
let lfo = max(Amplfo,0)
return generator * lfo }

Resources