How can i check if two SKSpriteNodes are near each other? like in a radius of 100. i am using the gamescene.swift and gamescene.sks.
SKSpriteNode has a position property with the (x, y).
Distance between two positions is sqrt((x1-x2)^2 + (y1-y2)^2)
So:
let dist = sqrt(pow(sk1.position.x - sk2.position.x, 2.0) + pow(sk1.position.y - sk2.position.y, 2.0))
if dist < 100 {
// they are close
}
This is center to center.
Based on #MartinR's comment, you could also
let dist = hypot(sk1.position.x - sk2.position.x, sk1.position.y - sk2.position.y)
Which does the distance function for you.
If you want to use built in SKPhysicsBody, then just set the body to a circle with radius of 100, then you can use the didBeginContact method when a contact occurs:
func setup()
{
let physicsBody1 = SKPhysicsBody(circleOfRadius:100.0)
physicsBody1.categoryBitMask = 1
physicsBody1.collisionBitMask = 0
physicsBody1.contactTestBitMask = 2
sprite1.physicsBody = physicsBody1
let physicsBody2 = SKPhysicsBody(circleOfRadius:100.0)
physicsBody2.categoryBitMask = 2
physicsBody2.collisionBitMask = 0
physicsBody2.contactTestBitMask = 1
sprite2.physicsBody = physicsBody2
}
func didBeginContact(contact:SKPhysicsContact)
{
//find some tutorials to your liking, and do your contact code here
}
Related
I wonder how this to be done . Please do not close this question I need suggestion or any hint to implement it.
Source https://www.youtube.com/watch?v=4uHyHRKmxZk
I have created A node which is SCNCylinder and drawn from target to the centre of the screen from updateAtTime method
Firstly I thought it was drawn with TextNode. SO I tried following
Inside that class I have method help to draw a node at each Unit I pass to UnitLength
func drawOtherBaseUnit(height:Float,intoThe unitType:UnitLength,toTheNode zAxisNode:SCNNode) {
print("---------------------------------------------------------------------")
let distanceInTarget = Converter.convert(value: Double(height), to: unitType).output + 1
print("DISTANCE",distanceInTarget)
print("---------------------------------------------------------------------")
var text = ""
switch unitType {
case .inches:
text = "INCH"
case .centimeters:
text = "CM"
default:
return
}
if !distanceInTarget.isNaN {
var distance = Int(distanceInTarget)
var i = 0
while distance > 0 {
print("distance ",distance)
let node = UnitNode(withPosition: SCNVector3Make(0, 0, 0), radius: CGFloat(Converter.convert(value: 0.1, from: unitType, to: UnitLength.meters).output),text:"\(text) \(i)",forType:unitType)
let valueIncreaseOnAsPerTarget = Int(distanceInTarget) - distance
let valueToIncreaseMeter = Converter.convert(value: Double(valueIncreaseOnAsPerTarget), from: unitType, to: UnitLength.meters).output
node.position.y = -Float(valueToIncreaseMeter)
node.position.x = 0
zAxisNode.addChildNode(node)
distance -= 1
i += 1
}
}
}
The UnitNode class is draw SCNText and some other nodes.
This is working fine. I can see node each provided unit.
But it if I use drawOtherBaseUnit for Inch as well as CM UI is Lagging it is not smooth.
Is it correct way to implement desired output ?
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 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!
I am new to swift a Sprite kit. In the app I am trying to make I have a submarine moving through the ocean. Every time the user clicks the screen the gravity starts pulling the sub in the opposite direction. My problem is that i can't find a way to keep the sub from leaving the screen. I have tried to solve it by making a physicsBody around the screen, but the sub still leaves the screen. I have also tried the following code in the updateCurrentTime fund.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.physicsWorld.gravity = CGVectorMake(0,gravity)
if (sub.position.y >= self.size.height - sub.size.height / 2){
sub.position.y = self.size.height - self.sub.size.height / 2
}
if (sub.position.y <= sub.size.height / 2) {
sub.position.y = self.sub.size.height / 2
}
}
But this doesn't do anything either.
Any help would be greatly appreciated!!!!! thanks in advance!
P.S. I can't believe that it is that hard to keep things on the screen!!!
frustrating!
Try SKConstraint - it doesn't require a physicsBody. The code would look something like this, and would constrain the sub sprite to the scene:
let width2 = sub.size.width/2
let height2 = sub.size.height/2
let xRange = SKRange(lowerLimit:0+width2,upperLimit:size.width-width2)
let yRange = SKRange(lowerLimit:0+height2,upperLimit:size.height-height2)
sub.constraints = [SKConstraint.positionX(xRange,Y:yRange)]
Try this in the update:
if sub.frame.maxY >= view!.frame.height {
sub.position.y = view!.frame.height - sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
if sub.frame.minY <= 0 {
sub.position.y = sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
And then inside of the event where you want to reverse gravity don't forget to do this:
sub.physicsBody!.affectedByGravity = true
Alternatively, instead of using gravity you could use this which is a better option in my opinion:
// This moves the object to the top of the screen
let action = SKAction.moveToY(view!.frame.height - character.size.height / 2, duration: 5.0) // Or however much time you want to the action to run.
action.timingMode = .EaseInEaseOut // Or something else
character.runAction(action)
// Change view!.frame.height - character.size.height / 2 to just character.size.height / 2 to move to the bottom.
So I'm playing around and slowly making my first iOS game. I'm trying to get my sprite object to set out a straight path angled towards the location of the player (which is where the user last touched).
The code I have makes the bee move towards the player and stop when it gets to it, instead of going from one side of the screen, through the player and off the other side of the screen.
var beeSpeed = 2.0
var moveAccross = SKAction.moveTo(CGPointMake(player.position.x,player.position.y), duration:beeSpeed)
badBee.runAction(moveAccross)
I will be having many objects constantly spawning that should all set their path once to the player location at the time they spawn.
Any help would be great,
I think I figured it out, if anyone could check my code and make sure it's doing the right thing that would be great :)
let actionMove = SKAction.moveTo(CGPoint(x: player.position.x-player.position.x-bee.size.width, y: player.position.y), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bee.runAction(SKAction.sequence([actionMove, actionMoveDone]))
You have to calculate the slope from the spawning point to the player and then interpolate to somewhere outside the screen.
func add(location:CGPoint)
{
let bee = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(10, 10))
bee.name = "Bee"
bee.position = location
self.addChild(bee)
let slopeToPlayer = (bee.position.y - player.position.y) / (bee.position.x - player.position.x)
let constant = bee.position.y - slopeToPlayer * bee.position.x
let finalX : CGFloat = bee.position.x < player.position.x ? 500.0 : -500.0 // Set it to somewhere outside screen size
let finalY = constant + slopeToPlayer * finalX
let distance = (bee.position.y - finalY) * (bee.position.y - finalY) + (bee.position.x - finalX) * (bee.position.x - finalX)
let beeSpeed : CGFloat = 100.0
let timeToCoverDistance = sqrt(distance) / beeSpeed
let moveAction = SKAction.moveTo(CGPointMake(finalX, finalY), duration: NSTimeInterval(timeToCoverDistance))
let removeAction = SKAction.runBlock { () -> Void in
bee.removeFromParent()
println("removed")
}
bee.runAction(SKAction.sequence([moveAction,removeAction]))
}