I am trying to build a Breakout game where I set the bricks exactly how I want them for each level using 1 SKSpriteNode. It all works, as in they spawn but way off screen to the bottom left. I am using the following line right at the start of didMove(to view: SKView):
scene?.anchorPoint = CGPoint(x: 0.0, y: 0.0)
And the code I use for spawning the bricks is:
//MARK: Brick Layout Config
let widthOfTiles = 7
let heightOfTiles = 3
let widthPoints = 50
let heightPoints = 20
// Different tile arrangements (e.g. 7 tiles wide, 3 high, 7*3 = 21 total tiles)
var brickArray = [false, true, false, true, false, true, false,
false, true, true, true, false, true, false,
false, true, false, true, false, true, false] // Says 'HI' using tiles
override func didMove(to view: SKView) {
//MARK: Brick
for tile in 0..<brickArray.count {
if brickArray[tile] == true {
let brick = SKSpriteNode(imageNamed: "red_brick")
brick.position = CGPoint(x: (tile % widthOfTiles) * (widthPoints) - (widthOfTiles / 2 * (widthPoints)),
y: ((tile - (tile % widthOfTiles)) / widthOfTiles) * (heightPoints) - (heightOfTiles / 2 * (heightPoints)))
brick.size = CGSize(width: widthPoints, height: heightPoints)
brick.physicsBody = SKPhysicsBody(rectangleOf: brick.frame.size)
brick.physicsBody?.friction = 0
brick.physicsBody?.allowsRotation = false
brick.physicsBody?.friction = 0
brick.physicsBody?.isDynamic = false
brick.physicsBody?.categoryBitMask = brickCategory
self.addChild(brick)
}
}
}
I cannot for the life of me get this to be in the center of the scene. Maybe someone can help me figure this out. Maths is a bad weak point of mine and while I think I got the equation right for the spawning, the position is way off.
If I am missing something that you need to see please let me know.
Unless you need an animation that depends on the anchor point I would not set it and leave it at its default value or it will change the visual representation of the position of the layer.
Related
I am trying to get my "word" to float across the screen; constant velocity, no impacts, no gravity, no friction. Everything works except the word slows down.
Code for creating word:
func createWordNode (word: String, atPos: CGPoint) -> SKSpriteNode {
let doneSize = CGSize(width: 50, height: 50)
let wordSprite = SKSpriteNode()
wordSprite.size = CGSize(width: doneSize.width * CGFloat(word.len()), height: doneSize.height)
wordSprite.position = atPos
wordSprite.blendMode = .replace
wordSprite.zPosition = zlvlBG + 1
let ltrs = Array(word.uppercased().characters)
for i in 0 ... ltrs.count - 1 {
let done = SKSpriteNode(imageNamed: "LetterTiles/Tile" + String(ltrs[i]) + ".png")
done.size = doneSize
done.position = CGPoint(x: doneSize.width * CGFloat(Double(i) - 1.5), y: 0)
done.blendMode = .replace
done.zPosition = zlvlBG + 1
wordSprite.addChild(done)
}
wordSprite.physicsBody?.restitution = 1
wordSprite.physicsBody?.friction = 0
wordSprite.physicsBody?.linearDamping = 0
wordSprite.physicsBody?.angularDamping = 0
wordSprite.physicsBody?.mass = 2000
wordSprite.physicsBody = SKPhysicsBody(rectangleOf: wordSprite.size)
wordSprite.physicsBody?.collisionBitMask = 0
wordSprite.physicsBody?.contactTestBitMask = 0
wordSprite.physicsBody?.categoryBitMask = categoryWords
wordSprite.physicsBody?.fieldBitMask = 0
wordSprite.physicsBody?.isDynamic = true
wordSprite.physicsBody?.affectedByGravity = false
wordSprite.physicsBody?.allowsRotation = false
var velocity = CGVector()
velocity.dx = 100
velocity.dy = 0
wordSprite.physicsBody?.velocity = velocity
wordSprite.physicsBody?.applyImpulse(velocity)
wordSprite.name = "Word:" + word
return wordSprite
}
I call function like:
addChild (createWordNode(word: "Done", atPos: CGPoint(x:-500, y:450)))
Any ideas why word slows down?
Thanks.
It looks like you are creating an object that doesn't need to be in the physics simulation. So you may want to reconsider simply dropping the physics related code and update the nodes position manually.
That being said if you wish this node to remain in the Physics simulation. You need to add the SKPhysicsBody to the SKSpriteNode before you start trying to modify properties. e.g.
wordSprite.physicsBody = SKPhysicsBody(rectangleOf: wordSprite.size)
wordSprite.physicsBody?.restitution = 1
wordSprite.physicsBody?.friction = 0
wordSprite.physicsBody?.linearDamping = 0
wordSprite.physicsBody?.angularDamping = 0
wordSprite.physicsBody?.mass = 2000
I worked with sprite a little bit. But, I am not an expert. Does it work for you to use an action instead of velocity as the following for example:
var moveRight = SKAction.moveTo(CGPointMake(400, 0), duration:2.0)
wordSprite.runAction(moveRight)
One thing to point out too. Why do you use applyImpulse. velocity should be enough. Did you try to remove it?
I have created some particles animations with specific sprites which works fine if I use them in the function:
override init(size: CGSize)
I use the following lines:
let sheet_particles = Particles()
let particles_node = SKSpriteNode(texture: sheet_particles.particle000())
particles_node.name = kparticles
particles_node.position = CGPoint(x: 500, y: 500)
particles_node.zPosition = 5
background.addChild(particles_node)
particles_node.runAction(particlesAction)
To make them appear in my scene.
The problem I have is if I try to use them in other functions in my scene, I can not see them.
func panForTranslation(translation : CGPoint) {
let position = selectedNode.position
if selectedNode.name! == kpuzzleNodeName {
selectedNode.position = CGPoint(x: position.x + translation.x * 2, y: position.y + translation.y * 2)
switch selectedNode.name2 {
case "0":
if selectedNode.frame.intersects(NPuzzle13.frame) {
particles_node.position = selectedNode.position
particles_node.runAction(particlesAction)
NPuzzle13.hidden = false
selectedNode.removeFromParent()
}
I see no particles sprite when the condition "0" happens but I see correctly the NPuzzle13. When I check the position of the particles_node node, its position is equal with the node selectedNode. All that is OK, except for the visibility of the particles... What am I missing? Thanks.
About zPosition seems all correct. I dont see any anchorPoint in your code.
I think your switch-case is jumped (not fired, not executed) because you check switch selectedNode.name2 instead of switch selectedNode.name
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!
Im importing an object in DAE format and I'm wanting to use it as a collision object however it is concave, but I'm having trouble implementing this in code:
func addCollisionBox() {
let collisionBoxNode = SCNNode()
let collisionBox = importedCollisionBox.rootNode.childNodeWithName("pCube2", recursively: false)
collisionBoxNode.addChildNode(collisionBox!)
collisionBoxNode.position = SCNVector3Make(0, 0, 0)
collisionBoxNode.scale = SCNVector3Make(30, 20, 50)
//collisionBoxNode.physicsBody = SCNPhysicsBody.staticBody()
collisionBoxNode.physicsBody = SCNPhysicsBody.bodyWithType(SCNPhysicsShapeTypeConcavePolyhedron, shape: collisionBox) // This line errors
collisionBoxNode.physicsBody?.restitution = 0.8
collisionBoxNode.name = "collisionBox"
theScene.rootNode.addChildNode(collisionBoxNode)
}
Cant get the line of code to work which has this in it SCNPhysicsShapeTypeConcavePolyhedron as commented in the code.
A "concave" physics body, per SCNPhysicsShapeTypeConcavePolyhedron, must still be "solid" in some sense, and the implied behavior for that collision type is to keep other bodies outside the "solid" form of the body. As such, your cube still has a "solid" interior even if you set its shape type to concave.
(What the concave shape type does do for you is let you make a solid shape that is non-convex — for example, a cube where one of the faces bows inward to produce a bowl shape.)
If you want to confine other bodies to a box-shaped volume, you'll need to create walls: that is, put a few physics bodies around the volume you want to enclose. Here's a quick utility function for something like that:
func wallsForBox(box: SCNBox, thickness: CGFloat) -> SCNNode {
func physicsWall(width: CGFloat, height: CGFloat, length: CGFloat) -> SCNNode {
let node = SCNNode(geometry: SCNBox(width: width, height: height, length: length, chamferRadius: 0))
node.physicsBody = .staticBody()
return node
}
let parent = SCNNode()
let leftWall = physicsWall(thickness, height: box.height, length: box.length)
leftWall.position.x = -box.width / 2
parent.addChildNode(leftWall)
let rightWall = physicsWall(thickness, height: box.height, length: box.length)
rightWall.position.x = box.width / 2
parent.addChildNode(rightWall)
let frontWall = physicsWall(box.width, height: box.height, length: thickness)
frontWall.position.z = box.length / 2
parent.addChildNode(frontWall)
let backWall = physicsWall(box.width, height: box.height, length: thickness)
backWall.position.z = -box.length / 2
parent.addChildNode(backWall)
let topWall = physicsWall(box.width, height: thickness, length: box.length)
topWall.position.y = box.height / 2
parent.addChildNode(topWall)
let bottomWall = physicsWall(box.width, height: thickness, length: box.length)
bottomWall.position.y = -box.height / 2
parent.addChildNode(bottomWall)
return parent
}
This creates a node hierarchy containing visible walls as separate nodes, but you could easily modify it to create invisible walls and/or a single node with a compound physics body. (See SCNPhysicsShape.)
I am building my first iPhone game using Xcode, SpriteKit and Swift. I am new to these technologies but I am familiar with general programming concepts.
Here is what I am trying to do in English. I want circles to randomly appear on the screen and then begin to expand in size. However, I do not want a circle to appear in a location where a circle currently exists. I am having trouble determining each circle's position.
Inside GameScene.swift I have the following code inside the didMoveToView:
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addCircle), SKAction.waitForDuration(3, withRange: 2)]
)))
The piece of code above calls my "addCircle" method:
func addCircle() {
// Create sprite.
let circle = SKSpriteNode(imageNamed: "grad640x640_circle")
circle.name = "circle"
circle.xScale = 0.1
circle.yScale = 0.1
// Determine where to position the circle.
let posX = random(min: 50, max: 270)
let posY = random(min: 50, max: 518)
// ***Check to see if position is currently occupied by another circle here.
circle.position = CGPoint(x: posX, y: posY)
// Add circle to the scene.
addChild(circle)
// Expand the circle.
let expand = SKAction.scaleBy(2, duration: 0.5)
circle.runAction(expand)
}
The random function above just chooses a random number within the given range. How can I check to see if my random functions are generating a location that is currently occupied by another circle?
I was thinking of using a do..while loop to randomly generate a set of x and y coordinates and then check to see if a circle is at that location but I cannot find how to check for that condition. Any help would be greatly appreciated.
There are a few methods which can help you in this regard:
(BOOL) intersectsNode: (SKNode*)node, available with SKNode, and
CGRectContainsPoint() as well as the CGRectContainsRect() methods.
For instance the loop to check for intersection can look as follows:
var point: CGPoint
var exit:Bool = false
while (!exit) {
let posX = random(min: 50, max: 270)
let posY = random(min: 50, max: 518)
point = CGPoint(x: posX, y: posY)
var pointFound: Bool = true
self.enumerateChildNodesWithName("circle", usingBlock: {
node, stop in
let sprite:SKSpriteNode = node as SKSpriteNode
if (CGRectContainsPoint(sprite.frame, point))
{
pointFound = false
stop.memory = true
}
})
if (pointFound)
{
exit = true
}
}
//point contains CGPoint where no other circle exists
//Declare new circle at point