Nodes Spawning off of screen in sprite kit swift - ios

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.

Related

Correctly position the camera when panning

I'm having a hard time setting boundaries and positioning camera properly inside my view after panning. So here's my scenario.
I have a node that is bigger than the screen and I want to let user pan around to see the full map. My node is 1000 by 1400 when the view is 640 by 1136. Sprites inside the map node have the default anchor point.
Then I've added a camera to the map node and set it's position to (0.5, 0.5).
Now I'm wondering if I should be changing the position of the camera or the map node when the user pans the screen ? The first approach seems to be problematic, since I can't simply add translation to the camera position because position is defined as (0.5, 0.5) and translation values are way bigger than that. So I tried multiplying/dividing it by the screen size but that doesn't seem to work. Is the second approach better ?
var map = Map(size: CGSize(width: 1000, height: 1400))
override func didMove(to view: SKView) {
(...)
let pan = UIPanGestureRecognizer(target: self, action: #selector(panned(sender:)))
view.addGestureRecognizer(pan)
self.anchorPoint = CGPoint.zero
self.cam = SKCameraNode()
self.cam.name = "camera"
self.camera = cam
self.addChild(map)
self.map.addChild(self.cam!)
cam.position = CGPoint(x: 0.5, y: 0.5)
}
var previousTranslateX:CGFloat = 0.0
func panned (sender:UIPanGestureRecognizer) {
let currentTranslateX = sender.translation(in: view!).x
//calculate translation since last measurement
let translateX = currentTranslateX - previousTranslateX
let xMargin = (map.nodeSize.width - self.frame.width)/2
var newCamPosition = CGPoint(x: cam.position.x, y: cam.position.y)
let newPositionX = cam.position.x*self.frame.width + translateX
// since the camera x is 320, our limits are 140 and 460 ?
if newPositionX > self.frame.width/2 - xMargin && newPositionX < self.frame.width - xMargin {
newCamPosition.x = newPositionX/self.frame.width
}
centerCameraOnPoint(point: newCamPosition)
//(re-)set previous measurement
if sender.state == .ended {
previousTranslateX = 0
} else {
previousTranslateX = currentTranslateX
}
}
func centerCameraOnPoint(point: CGPoint) {
if cam != nil {
cam.position = point
}
}
Your camera is actually at a pixel point 0.5 points to the right of the centre, and 0.5 points up from the centre. At (0, 0) your camera is dead centre of the screen.
I think the mistake you've made is a conceptual one, thinking that anchor point of the scene (0.5, 0.5) is the same as the centre coordinates of the scene.
If you're working in pixels, which it seems you are, then a camera position of (500, 700) will be at the top right of your map, ( -500, -700 ) will be at the bottom left.
This assumes you're using the midpoint anchor that comes default with the Xcode SpriteKit template.
Which means the answer to your question is: Literally move the camera as you please, around your map, since you'll now be confident in the knowledge it's pixel literal.
With one caveat...
a lot of games use constraints to stop the camera somewhat before it gets to the edge of a map so that the map isn't half off and half on the screen. In this way the map's edge is showing, but the furthest the camera travels is only enough to reveal that edge of the map. This becomes a constraints based effort when you have a player/character that can walk/move to the edge, but the camera doesn't go all the way out there.

Projectile not originating from the correct location

I am trying to make a SKSpriteNode come from a SKShapeNode. When the below code runs, the projectiles are appearing, but they will originate from a different point on the screen, not the player location.
Here is my shoot function that is in my Player Class.
func shoot() {
let newProjectile = Projectile()
newProjectile.position = self.position
self.addChild(newProjectile)
let action = SKAction.moveTo(CGPointMake(
600 * -cos(newProjectile.zRotation - 1.57079633) + newProjectile.position.x,
600 * -sin(newProjectile.zRotation - 1.57079633) + newProjectile.position.y
), duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
newProjectile.runAction(SKAction.sequence([action, actionMoveDone]))
}
Here is my Projectile Class :
class Projectile : SKSpriteNode {
let Texture = SKTexture(imageNamed: "image.png")
static var counter : Int = 0
init(){
//super.init()
super.init(texture: Texture, color: UIColor.whiteColor(), size: CGSize(width: radius * 2, height: radius * 2))
self.name = "projectile-" + NSUUID().UUIDString
self.physicsBody = SKPhysicsBody(circleOfRadius: radius)
self.physicsBody?.categoryBitMask = GlobalConstants.Category.projectile
self.physicsBody?.collisionBitMask = GlobalConstants.Category.projectile
self.physicsBody?.contactTestBitMask = GlobalConstants.Category.projectile | GlobalConstants.Category.wall
self.zPosition = GlobalConstants.ZPosition.projectile
}
}
you probably need to add your projectiles to the scene instead of the player itself. you're adding the projectiles to your player.
you should add the projectile to the parents scene. something like this.
self.scene!.addChild(newProjectile)
If you want the projectiles to be children of the player, then the projectile's position must be expressed in terms of the player i.e. A position of (0, 0) will place the projectile at the player's anchor point.
If you want the projectiles to be children of the scene, then the projectile's position will be expressed in terms of the scene i.e. a position equal to the player's position will be needed to put the projectile on top of the player.
You combined the two, causing the projectile to be placed with the (x,y) position of the player in the scene, but relatIve to the player, which means that wherever the player is compared to the scene's origin, then that's where the projectile will appear relative to the player.

Custom Particle System for iOS

I want to create a particle system on iOS using sprite kit where I define the colour of each individual particle. As far as I can tell this isn't possible with the existing SKEmitterNode.
It seems that best I can do is specify general behaviour. Is there any way I can specify the starting colour and position of each particle?
This can give you a basic idea what I was meant in my comments. But keep in mind that it is untested and I am not sure how it will behave if frame rate drops occur.
This example creates 5 particles per second, add them sequentially (in counterclockwise direction) along the perimeter of a given circle. Each particle will have different predefined color. You can play with Settings struct properties to change the particle spawning speed or to increase or decrease number of particles to emit.
Pretty much everything is commented, so I guess you will be fine:
Swift 2
import SpriteKit
struct Settings {
static var numberOfParticles = 30
static var particleBirthRate:CGFloat = 5 //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}
class GameScene: SKScene {
var positions = [CGPoint]()
var colors = [SKColor]()
var emitterNode:SKEmitterNode?
var currentPosition = 0
override func didMoveToView(view: SKView) {
backgroundColor = .blackColor()
emitterNode = SKEmitterNode(fileNamed: "rain.sks")
if let emitter = emitterNode {
emitter.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
emitter.particleBirthRate = Settings.particleBirthRate
addChild(emitter)
let radius = 50.0
let center = CGPointZero
for var i = 0; i <= Settings.numberOfParticles; i++ {
//Randomize color
colors.append(SKColor(red: 0.78, green: CGFloat(i*8)/255.0, blue: 0.38, alpha: 1))
//Create some points on a perimeter of a given circle (radius = 40)
let angle = Double(i) * 2.0 * M_PI / Double(Settings.numberOfParticles)
let x = radius * cos(angle)
let y = radius * sin(angle)
let currentParticlePosition = CGPointMake(CGFloat(x) + center.x, CGFloat(y) + center.y)
positions.append(currentParticlePosition)
if i == 1 {
/*
Set start position for the first particle.
particlePosition is starting position for each particle in the emitter's coordinate space. Defaults to (0.0, 0,0).
*/
emitter.particlePosition = positions[0]
emitter.particleColor = colors[0]
self.currentPosition++
}
}
// Added just for debugging purposes to show positions for every particle.
for particlePosition in positions {
let sprite = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 1, height: 1))
sprite.position = convertPoint(particlePosition, fromNode:emitter)
sprite.zPosition = 2
addChild(sprite)
}
let block = SKAction.runBlock({
// Prevent strong reference cycles.
[unowned self] in
if self.currentPosition < self.positions.count {
// Set color for the next particle
emitter.particleColor = self.colors[self.currentPosition]
// Set position for the next particle. Keep in mind that particlePosition is a point in the emitter's coordinate space.
emitter.particlePosition = self.positions[self.currentPosition++]
}else {
//Stop the action
self.removeActionForKey("emitting")
emitter.particleBirthRate = 0
}
})
// particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.
let rate = NSTimeInterval(CGFloat(1.0) / Settings.particleBirthRate)
let sequence = SKAction.sequence([SKAction.waitForDuration(rate), block])
let repeatAction = SKAction.repeatActionForever(sequence)
runAction(repeatAction, withKey: "emitting")
}
}
}
Swift 3.1
import SpriteKit
struct Settings {
static var numberOfParticles = 30
static var particleBirthRate:CGFloat = 5 //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}
class GameScene: SKScene {
var positions = [CGPoint]()
var colors = [SKColor]()
var emitterNode: SKEmitterNode?
var currentPosition = 0
override func didMove(to view: SKView) {
backgroundColor = SKColor.black
emitterNode = SKEmitterNode(fileNamed: "rain.sks")
if let emitter = emitterNode {
emitter.position = CGPoint(x: frame.midX, y: frame.midY)
emitter.particleBirthRate = Settings.particleBirthRate
addChild(emitter)
let radius = 50.0
let center = CGPoint.zero
for var i in 0...Settings.numberOfParticles {
//Randomize color
colors.append(SKColor(red: 0.78, green: CGFloat(i * 8) / 255.0, blue: 0.38, alpha: 1))
//Create some points on a perimeter of a given circle (radius = 40)
let angle = Double(i) * 2.0 * Double.pi / Double(Settings.numberOfParticles)
let x = radius * cos(angle)
let y = radius * sin(angle)
let currentParticlePosition = CGPoint.init(x: CGFloat(x) + center.x, y: CGFloat(y) + center.y)
positions.append(currentParticlePosition)
if i == 1 {
/*
Set start position for the first particle.
particlePosition is starting position for each particle in the emitter's coordinate space. Defaults to (0.0, 0,0).
*/
emitter.particlePosition = positions[0]
emitter.particleColor = colors[0]
self.currentPosition += 1
}
}
// Added just for debugging purposes to show positions for every particle.
for particlePosition in positions {
let sprite = SKSpriteNode(color: SKColor.orange, size: CGSize(width: 1, height: 1))
sprite.position = convert(particlePosition, from: emitter)
sprite.zPosition = 2
addChild(sprite)
}
let block = SKAction.run({
// Prevent strong reference cycles.
[unowned self] in
if self.currentPosition < self.positions.count {
// Set color for the next particle
emitter.particleColor = self.colors[self.currentPosition]
// Set position for the next particle. Keep in mind that particlePosition is a point in the emitter's coordinate space.
emitter.particlePosition = self.positions[self.currentPosition]
self.currentPosition += 1
} else {
//Stop the action
self.removeAction(forKey: "emitting")
emitter.particleBirthRate = 0
}
})
// particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.
let rate = TimeInterval(CGFloat(1.0) / Settings.particleBirthRate)
let sequence = SKAction.sequence([SKAction.wait(forDuration: rate), block])
let repeatAction = SKAction.repeatForever(sequence)
run(repeatAction, withKey: "emitting")
}
}
}
Orange dots are added just for debugging purposes and you can remove that part if you like.
Personally I would say that you are overthinking this, but I might be wrong because there is no clear description of what you are trying to make and how to use it. Keep in mind that SpriteKit can render a bunch of sprites in a single draw call in very performant way. Same goes with SKEmitterNode if used sparingly. Also, don't underestimate SKEmitterNode... It is very configurable actually.
Here is the setup of Particle Emitter Editor:
Anyways, here is the final result:
Note that nodes count comes from an orange SKSpriteNodes used for debugging. If you remove them, you will see that there is only one node added to the scene (emitter node).
What you want is completely possible, probably even in real time. Unfortunately to do such a thing the way you describe with moving particles as being a particle for each pixel would be best done with a pixel shader. I don't know of a clean method that would allow you to draw on top of the scene with a pixel shader otherwise all you would need is a pixel shader that takes the pixels and moves them out from the center. I personally wouldn't try to do this unless I built the game with my own custom game engine in place of spritekit.
That being said I'm not sure a pixel per pixel diffusion is the best thing in most cases. Expecially if you have cartoony art. Many popular games will actually make sprites for fragments of the object they expect to shader. So like if it's an airplane you might have a sprite for the wings with perhaps even wires hanging out of this. Then when it is time to shatter the plane, remove it from the scene and replace the area with the pieces in the same shape of the plane... Sorta like a puzzle. This will likely take some tweaking. Then you can add skphysicsbodies to all of these pieces and have a force push them out in all directions. Also this doesn't mean that each pixel gets a node. I would suggest creatively breaking it into under 10 pieces.
And as whirlwind said you could all ways get things looking "like" it actually disintegrated by using an emitter node. Just make the spawn area bigger and try to emulate the color as much as possible. To make the ship dissappear you could do a fade perhaps? Or Mabye an explosion sprite over it? Often with real time special effects and physics, or with vfx it is more about making it look like reality then actually simulating reality. Sometimes you have to use trickery to get things to look good and run real-time.
If you want to see how this might look I would recommend looking at games like jetpac joyride.
Good luck!

How to "center" SKTexture in SKSpriteNode

I'm trying to make Jigsaw puzzle game in SpriteKit. To make things easier I using 9x9 squared tiles board. On each tile is one childNode with piece of image from it area.
But here's starts my problem. Piece of jigsaw puzzle isn't perfect square, and when I apply SKTexture to node it just place from anchorPoint = {0,0}. And result isn't pretty, actually its terrible.
https://www.dropbox.com/s/2di30hk5evdd5fr/IMG_0086.jpg?dl=0
I managed to fix those tiles with right and top "hooks", but left and bottom side doesn't care about anything.
var sprite = SKSpriteNode()
let originSize = frame.size
let textureSize = texture.size()
sprite.size = originSize
sprite.texture = texture
sprite.size = texture.size()
let x = (textureSize.width - originSize.width)
let widthRate = x / textureSize.width
let y = (textureSize.height - originSize.height)
let heightRate = y / textureSize.height
sprite.anchorPoint = CGPoint(x: 0.5 - (widthRate * 0.5), y: 0.5 - (heightRate * 0.5))
sprite.position = CGPoint(x: frame.width * 0.5, y: frame.height * 0.5)
addChild(sprite)
Can you give me some advice?
I don't see a way you can get placement right without knowing more about the piece texture you are using because they will all be different. Like if the piece has a nob on any of the sides and the width width/height the nob will add to the texture. Hard to tell in the pic but even if it doesn't have a nob and instead has an inset it might add varying sizes.
Without knowing anything about how the texture is created I am not able to offer help on that. But I do believe the issue starts with that. If it were me I would create a square texture with additional alpha to center the piece correctly. So the center of that texture would always be placed in the center of a square on the grid.
With all that being said I do know that adding that texture to a node and then adding that node to a SKNode will make your placement go smoother with the way you currently have it. The trick will then only be placing that textured piece correctly within the empty SKNode.
For example...
let piece = SKSpriteNode()
let texturedPiece = SKSpriteNode(texture: texture)
//positioning
//offset x needs to be calculated with additional info about the texture
//for example it has just a nob on the right
let offsetX : CGFloat = -nobWidth/2
//offset y needs to be calculated with additional info about the texture
//for example it has a nob on the top and bottom
let offsetY : CGFloat = 0.0
texturedPiece.position = CGPointMake(offsetX, offsetY)
piece.addChild(texturedPiece)
let squareWidth = size.width/2
//Now that the textured piece is placed correctly within a parent
//placing the parent is super easy and consistent without messing
//with anchor points. This will also make rotations nice.
piece.position = CGPoint(x: squareWidth/2, y: squareWidth/2)
addChild(piece)
Hopefully that makes sense and didn't confuse things further.

Prevent sprite rotation from changing gravity

I have a function in SpriteKit that spawns a sprite (which is a square) at the top of the screen, and then gravity pulls the sprite to the bottom of the screen. I'm trying to get the sprite to rotate smoothly for an indefinite amount of time until it is removed when it reaches the bottom of the screen. The following code is in the class for the sprite:
func rotate() {
var action = SKAction.rotateByAngle(CGFloat(M_PI_2), duration: NSTimeInterval(1.5))
var repeatAction = SKAction.repeatActionForever(action)
self.runAction(repeatAction)
}
The problem that I am having is that, as the sprite turns, the sprite travels in the direction of the bottom of itself, not towards the bottom of the screen (the direction gravity is supposed to be). To further clarify, the object rotates, but as it rotates to 90 degrees, it travels sideways instead of downwards. This doesn't make sense to me. This is the code I'm using to add gravity in the didMoveToView() function:
self.physicsWorld.gravity = CGVectorMake(0.0, -1.8)
and this is the code used to spawn the sprite (the rs.rotate() calls the rotate method that is listed above):
func spawnRedSquares() {
if !self.gameOver {
let rs = RedSquare()
var rsSpawnRange = randomNumberBetween(self.leftSideBar.position.x + rs.sprite.size.width / 2, secondNum: self.rightSideBar.position.x - rs.sprite.size.width / 2)
rs.position = CGPointMake(rsSpawnRange, CGRectGetMaxY(self.frame) + rs.sprite.size.height * 2)
rs.zPosition = 3
self.addChild(rs)
self.rsArray.append(rs)
rs.rotate()
let spawn = SKAction.runBlock(self.spawnRedSquares)
let delay = SKAction.waitForDuration(NSTimeInterval(timeBetweenRedSquares))
let spawnThenDelay = SKAction.sequence([delay, spawn])
self.runAction(spawnThenDelay)
}
}
How can I get the object to rotate, but still fall down as if it were affected by normal gravity?
It looks like you are adding the rotation action to 'self' which I would assume is your scene as opposed to your sprite. This is causing the entire scene to rotate, and therefore its gravity is rotating as well.
Add the rotating action to your sprite and that should solve the issue.
Example: assuming your square sprite is called squareSprite:
let action = SKAction.rotateByAngle(CGFloat(M_PI_2), duration: NSTimeInterval(2))
let repeatAction = SKAction.repeatActionForever(action)
squareSprite.runAction(repeatAction) //Add the repeatAction to your square sprite

Resources