I am fairly new to swift and Sprite Kit. I am trying create a simple game using SKSpriteNodes. My game needs to keep track the sequence of the "clicked" nodes as it needs to be processed based on its sequence. I am thinking of creating a list, as the nodes are clicked it will be added to the list. Is this the easiest way? I am thinking that it may consume more memory. My nodes are of the same names. Below is the method I used in adding the nodes in my scene. I need to know the sequence of colored balls clicked.
for x in listNodes{
var ballNode = SKSpriteNode(imageNamed: String(color))
ballNode.name = "ball"
var point: CGPoint = CGPointMake(0,0)
var done: Bool = false
let randomX = randomRange(CGRectGetMinX(self.frame), max: CGRectGetMaxX(self.frame)-(ballNode.size.width))
let randomY = randomRange(CGRectGetMinY(self.frame) + self.border.size.height, max: CGRectGetMaxY(self.frame)-ballNode.size.height)
point = CGPointMake(randomX, randomY)
ballNode.anchorPoint = CGPointMake(0,0)
ballNode.position = point
self.addChild(ballNode)
}
Related
In my original build, my sprites were static, so to make the collision physics as accurate as I could rather than use rectangleOfSize for SKPhysicsBody passing in the node, I used this tool to define a path SpriteKit's SKPhysicsBody with polygon helper tool.
However now I'm animating the sprites so that they move back and forth during gameplay (obviously my physics path remains static given the above) so my physicsbody no longer matches what the player sees on screen.
The helper tool seemed like a bit of hack that Apple would eventually fix in the API, has there been anything recent in SpriteKit that would help me out here so that I can pass in the node and define a precise physicsbody rather than the hard-coded approach? If not, any other alternatives?
Not sure how performant this is but you can pre-generate the physics bodies for each texture then animate the sprite along with its physicsBody using a custom action.
Here is an example:
func testAnimation() {
var frameTextures = [SKTexture]()
var physicsBodies = [SKPhysicsBody]()
for index in 1...8 {
// The animation has 8 frames
let texture = SKTexture(imageNamed: "guy\(index)")
frameTextures.append(texture)
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.affectedByGravity = false
physicsBodies.append(physicsBody)
}
let sprite = SKSpriteNode(texture: frameTextures[0])
let framesPerSecond = 16.0
let animation = SKAction.customAction(withDuration: 1.0, actionBlock: { node, time in
let index = Int((framesPerSecond * Double(time))) % frameTextures.count
if let spriteNode = node as? SKSpriteNode {
spriteNode.texture = frameTextures[index]
spriteNode.physicsBody = physicsBodies[index]
}
})
sprite.run(SKAction.repeatForever(animation));
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)
}
I am creating a space shooter game. I have created an enemy node. What I want to do is have 4 of those nodes in each corner of the screen. In other words, I want to spawn multiple copies of the same node at the same time. When the game loads, I want there to be four nodes, that are all the same. How can I do this?
Thanks
Here's a section of code I use to generate a row of 4 space invaders. I reuse the same SKSpriteNode() variable as once the node has been added to the scene, the variable is no longer required:
for invaderPosition in 100.stride(to: 500, by: 120) {
invader = SKSpriteNode(texture: texturesA[0])
invader.position = CGPoint(x: CGFloat(invaderPosition), y: 200)
invader.xScale = 8
invader.yScale = 6
invader.color = SKColor.redColor()
invader.colorBlendFactor = 1.0
invader.name = "InvaderA"
invader.texture = texturesA[1]
invader.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(texturesA, timePerFrame: self.timePerMove)))
addChild(invader)
}
invader is defined as an SKSpriteNode property. texturesA is just an array of SKTextures; timePerMove is just a time interval after which the invader should move (in Update) and change shape(texture).
var invader = SKSpriteNode()
var texturesA:[SKTexture] = []
let timePerMove: CFTimeInterval = 1.0
So you could do something similar except the invader's positions would be the 4 corners rather than a row.
Just use the copy command,
let newNode = originalNode.copy() as! SKSpriteNode;
I am building a ios game with swift and I have run into a bit of a problem. I am trying to spawn balls from the top of the screen and have them come down towards the ground. They are supposed to have random x values and go down at random rates but instead of spawning on the screen the nodes spawn on an x value which is not encompassed by the screen. Please help me as I think I have done everything right.
Here is the code for my addball function...
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person & PhysicsCategory.Ground
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.frame.size.width/2+1, max: self.frame.size.width - ball.frame.size.width/2-1)
println(actualX)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
ball.runAction(SKAction.sequence([actionMove, actionMoveDone]), withKey: "action")
}
here is the code for my random functions
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
The problem here is that your SKScene likely takes up much more space than the screen of your device. Thus, when you calculate a random value using the whole scene, some of the time the ball will spawn in the area of the scene not visible to you.
The two main properties that control the scene's size are its size and scaleMode properties. The scaleMode property relates to how the scene is mapped. Unless you initialized and presented this scene yourself, you can check the scaleMode in your view controller. It will likely be set to aspectFill, which according to Apple means:
The scaling factor of each dimension is calculated and the larger of the two is chosen. Each axis of the scene is scaled by the same scaling factor. This guarantees that the entire area of the view is filled but may cause parts of the scene to be cropped.
If you don't like this, there are other scaleModes. However, in most cases this mode would actually be preferable since SpriteKit's internal scaling is able to make universal apps. If this is fine for you, then the easiest thing to do is set hardcoded values for something like the spawn locations for your ball node.
I have a spritekit scene with various objects on it. I want to be able to remove these objects in a wide circle around a user's touch event. In my touchesBegan function I have the following code which creates a physicsbody and checks if it contacts any objects:
var bigNode = SKShapeNode(circleOfRadius: 100)
bigNode.position = touchLocation!
var bigCircle = SKPhysicsBody(circleOfRadius: 100, center: touchLocation!)
bigNode.physicsBody = bigCircle
bigNode.strokeColor = SKColor.yellowColor()
bigNode.zPosition = CGFloat.max
bigNode.physicsBody?.dynamic = false
bigNode.physicsBody?.categoryBitMask = ColliderType.Beam.rawValue
bigNode.physicsBody?.contactTestBitMask = ColliderType.Target.rawValue
bigNode.physicsBody?.collisionBitMask = ColliderType.Target.rawValue
bigNode.name = "temp"
gameLayer.addChild(bigNode)
if let targets = bigNode.physicsBody?.allContactedBodies(){
for target in targets{
removeTarget(target as? SKPhysicsBody, location: target.position)
}
}
I only draw the circle so I can explicitly see what should be contacted in the simulator. The call bigNode.physicsBody?.allContactedBodies() always returns an empty array, even when I can see clearly that there are objects in the circle.
Does spritekit need a tick of update or something like that in order for that array to be populated? What am I doing wrong here?
For more info, here's the Bitmasks for the targets
target.physicsBody?.categoryBitMask = ColliderType.Target.rawValue
target.physicsBody?.contactTestBitMask = ColliderType.Target.rawValue | ColliderType.Wall.rawValue | ColliderType.Beam.rawValue
target.physicsBody?.collisionBitMask = ColliderType.Wall.rawValue | ColliderType.Target.rawValue | ColliderType.Beam.rawValue
And here's my collider enum:
enum ColliderType: UInt32 {
case Target = 1
case Beam = 2
case Wall = 4
}
From what I understand, when you touch somewhere : you create an area (bigNode), from which each node (target) inside will be removed.
The allContactedBodies will return an array of SKPhysicsBody objects that this body is in contact with. However, this as to run in the update method apparently. Which is very costly in processing time.
That said, you might want to try it. But you could also try to stick with the didBeginContact method.
I'm trying to figure out how to optimize my game. I have a lot of turrets etc shooting out physics bodies. The game is playable but can dip into 50 or 45 fps. I've figured out that it is related to adding objects to my scene on the fly.
for example. when my turret is shooting it runs this code
func shootBlaster(){
let blaster = Projectile(color: SKColor.cyanColor(), size: CGSize(width: convertNum(3), height: convertNum(3)))
blaster.name = "blaster"
blaster.physicsBody = SKPhysicsBody(rectangleOfSize: blaster.size)
blaster.physicsBody!.categoryBitMask = CategoryEnemyProjectile
blaster.physicsBody!.contactTestBitMask = CategoryShip | CategoryShipShield
blaster.physicsBody!.collisionBitMask = 0
let fireAction = SKAction.runBlock({
let angle = self.turret.base.zRotation + self.zRotation
let velocity = CGPoint(angle: angle) * convertNum(420)
let vector = CGVectorMake(velocity.x, velocity.y)
let blas = blaster.copy() as Projectile
blas.wpnDmg = 10
blas.position = self.gameScene.gameLayer.convertPoint(self.turret.barrelPos, fromNode: self.turret.base)
self.gameScene.gameLayer.addChild(blas)
blas.physicsBody!.velocity = vector
blas.zRotation = blas.physicsBody!.velocity.angle
blas.runAction(SKAction.removeFromParentAfterDelay(1))
})
let recoilAction = SKAction.moveByX(-5, y: 0, duration: 0.08)
let reverseRecoil = recoilAction.reversedAction()
self.turret.barrel.runAction(SKAction.repeatAction(
SKAction.sequence([
recoilAction,
fireAction,
reverseRecoil
])
,count: self.blasterNum))
}
you can see that I'm adding a "blaster" inside of each of the fireAction blocks. I've noticed games with better performance are rarely adding children at runtime. It seems best to have everything pre-loaded. It makes sense that loading resources during runtime would put a strain on things, but what is the alternative? Do I need to add everything to the scene ahead of time and somehow hide and show it?
With any kind of shooting game where you have numerous projectiles you want to utilize "pooling". Create a pool of projectiles and reuse them as opposed to creating new ones when you need them.