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.
Related
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.
In my app I want to create a physics body for a node programmatically. However when I create the physics body programmatically, it doesn't seem to work, although the physics body created in the SpriteKit editor does work. When the physicsbody is created programmatically, it does not collide with another node, when it is created in the editor, it does.
Here is my code:
physicsBody = SKPhysicsBody(rectangleOf: size)
if let body = self.physicsBody{
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = false
body.categoryBitMask = CollisionCategoryBitmask.shootWall.rawValue
body.collisionBitMask = CollisionCategoryBitmask.circle.rawValue
body.contactTestBitMask = CollisionCategoryBitmask.circle.rawValue
}
size is the size property of the node.
When I now comment out the first line where the physics body is assigned to the node and instead set the physics body in the SpriteKit scene editor it does work. Note that the code inside the if condition is being executed in both cases.
Update:
What is interesting is that when I do this:
physicsBody = SKPhysicsBody(bodies: [physicsBody!])
which shouldn't have any impact because it its basically changing nothing, then it doesn't work as well. Is this a bug in SpriteKit?
When you do this
required init?(coder aDecoder: NSCoder) {
factor = 0.5
super.init(coder: aDecoder)
self.pBody()
}
physicsWorld does not exist yet, so the body does not get placed in the world.
You want to do it after it is created, so throw your code into a closure to be launched after your setup completes. You can do this using DispatchQueue
required init?(coder aDecoder: NSCoder) {
factor = 0.5
super.init(coder: aDecoder)
DispatchQueue.main.async {
self.pBody() //This will fire after the SKS finished building the scene, so physicsWorld will exist
}
}
Trying to learn how to use GameplayKit, and in particular, agents & behaviors. Trying to boil down all the tutorials and examples out there into a small, simple piece of code that I can use as a starting point for my own app. Unfortunately, what I've come up with doesn't work properly, and I can't figure out why. It's just a simple sprite with a simple GKGoal(toWander:). Rather than wandering, it just moves in a straight line to the right, forever. It also starts out impossibly slowly and speeds up impossibly slowly, despite my setting the max speed & acceleration to ridiculously high values. I can't figure out the fundamental difference between my simple code and all the complex examples out there. Here's the code, minus required init?(coder aDecoder: NSCoder):
class GremlinAgent: GKAgent2D {
override init() {
super.init()
maxAcceleration = 100000
maxSpeed = 1000000
radius = 20
}
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
let goal = GKGoal(toWander: 100)
behavior = GKBehavior(goal: goal, weight: 1)
}
}
class Gremlin: GKEntity {
let sprite: SKShapeNode
init(scene: GameScene) {
sprite = SKShapeNode(circleOfRadius: 20)
sprite.fillColor = .blue
scene.addChild(sprite)
super.init()
let agent = GremlinAgent()
addComponent(agent)
let node = GKSKNodeComponent(node: sprite)
addComponent(node)
agent.delegate = node
}
}
And in GameScene.swift, didMove(to view:):
let gremlin = Gremlin(scene: self)
entities.append(gremlin)
Can anyone help me out?
As has been pointed out elsewhere, you have to set the weight for the goal very high. Try 100, or even 1000, and notice the difference in behavior. But even with these large weights, there's still a problem in your example: the maxSpeed value. You can't set it so high, or your sprite will just fly off in a straight line. Set it to a value closer to the speed you set in the GKGoal object.
Also notice that the wandering will always start off in the direction your sprite is pointing, so if you don't want it to always start off moving to the right, set zRotation to some random value.
Finally, don't create a new behavior in every call to update(). For wandering, you can just set it once, say, in init().
Here's some code that works:
class GremlinAgent: GKAgent2D {
override init() {
super.init()
maxAcceleration = 100000
maxSpeed = 100
let goal = GKGoal(toWander: 100)
behavior = GKBehavior(goal: goal, weight: 1000)
}
}
class Gremlin: GKEntity {
let sprite: SKShapeNode
init(scene: GameScene) {
sprite = SKShapeNode(circleOfRadius: 20)
sprite.fillColor = .blue
sprite.zRotation = CGFloat(GKRandomDistribution(lowestValue: 0, highestValue: 360).nextInt())
scene.addChild(sprite)
super.init()
let agent = GremlinAgent()
addComponent(agent)
let node = GKSKNodeComponent(node: sprite)
addComponent(node)
agent.delegate = node
}
}
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.
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.