I'm trying to have a rain particles which are affected by wind aka physicsWorld gravity.
I can see that the gravity does has an affect on my SKSpriteNodes but I can't achieve the same affect on an SKEmitterNode.
I'm just wondering if it's possible.
Here's what I've been trying...
override func didMove(to view: SKView) {
if let rainParticles = SKEmitterNode(fileNamed: "Rain.sks") {
rainParticles.position = CGPoint(x: size.width/2, y: size.height)
rainParticles.name = "rainParticle"
rainParticles.targetNode = scene
rainParticles.particlePositionRange =
CGVector(dx: frame.size.width, dy: frame.size.height)
rainParticles.zPosition = -1
// I don't think this is right
rainParticles.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
rainParticles.physicsBody?.affectedByGravity = true
addChild(rainParticles)
}
physicsWorld.contactDelegate = self
// gravity is pushing to the right here
physicsWorld.gravity = CGVector(dx: 20, dy: 0)
physicsWorld.speed = 0.85
}
Yes I have added SKPhysicsContactDelegate.
Obviously I want to ignore collisions so I haven't set collisionBitMask, also I don't want to have rain bouncing off anything with contactTestBitMask. I don't believe I need to set a categoryBitMask.
Particles are not represented by objects in SpriteKit. This means you cannot perform node-related tasks on particles, nor can you associate physics bodies with particles to make them interact with other content. Although there is no visible class representing particles added by the emitter node, you can think of a particle as having properties like any other object.
This is straight from SKEmitterNode documentation. Particles won't get any gravity acceleration from the physicsWorld of the scene.
Also rainParticles.physicsBody refers to the SKEmitterNode physicsBody, not its particles.
If you simply want the particles to simulate the current physicsWorld's gravity:
rainParticles.xAcceleration = self.physicsWorld.gravity.dx
rainParticles.yAcceleration = self.physicsWorld.gravity.dy
Related
is it somehow possible to destroy an object on contact? Like not just delete it from the screen with body.removeFromParent(), I would like to have an animation.
I have a player and walls, and when the player has a special powerup, I want it to be able to destroy the walls on contact. I could imagine that I have like the wall split up as many little physics bodies and they hold together on like an anchor point and when my player hits it, they get an impulse from the player (just set isDynamic to true I guess) and losen the anchor point so all the sprite Nodes will fly their way and so the wall will be destroyed.
Can you give me some help / advise of a good way of doing that?
You don't need to have the nodes making up the wall held together in any way - just place them on the screen. If the player doesn't have the power-up, turn off the bit for the player in the wall nodes' physicsBodies collisionBitMask so that the wall nodes do not collide with the player. Then when the player hits the wall, the player will be affected by the collision (and bounce off) but the wall nodes will be unaffected.
When the player has the power-up, make the wall nodes affected by the collision and also turn on contacts between the player and the wall (it's enough just to turn on the bit for the wall category in the player's contactTestBitMask). Then the wall nodes will be affected by the collision (and move or spin away) and your didBegin() will be called and you can run an action on each wall node comprising of the animation you want and ending with removeFromParent().
A guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420
Manipulating bit masks to turn collision & contacts off and on.
https://stackoverflow.com/a/46495864/1430420
Edit: SK demo showing an object hitting a wall made up of blocks:
Create a new SK project and use this as the GameScene,swift:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx:0, dy:0)
let ball = SKSpriteNode.init(color: .red, size: CGSize(width: 50, height: 50))
ball.physicsBody = SKPhysicsBody.init(circleOfRadius: ball.size.width/2)
ball.position = CGPoint(x: 0, y: 0)
buildWall()
addChild(ball)
ball.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
}
func buildWall() {
let xStart : CGFloat = ((scene?.size.width)!/2) * -0.9
var brickPosition = CGPoint(x: xStart, y: 500)
let brickSize = CGSize(width: 20, height:20)
for wallRow in 1...10 {
for wallColumn in 1...30 {
let brick = SKSpriteNode(color: .yellow, size: brickSize)
brick.physicsBody = SKPhysicsBody.init(rectangleOf: brick.size)
brick.position = brickPosition
addChild(brick)
brickPosition.x += brickSize.width + 1
}
brickPosition.x = xStart
brickPosition.y -= 11
}
}
}
I'm new with sprite kit. I have tried simple ball bouncing game with 2 player, another is tracking the ball slowly. But I have discovered a problem. When I move the line to ball (with edge) ball disappearing from the screen. Another times not a problem, ball bouncing. What is the problem?
I have one GameScene, sks and ViewController. My sprite nodes coming from sks. If someone explain this case. It would be better. I have attached what I did below.
My GameScene:
class GameScene: SKScene {
var ball = SKSpriteNode()
var enemy = SKSpriteNode()
var main = SKSpriteNode()
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
enemy = self.childNode(withName: "enemy") as! SKSpriteNode
main = self.childNode(withName: "main") as! SKSpriteNode
ball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
}
View controller:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
}
override var prefersStatusBarHidden: Bool {
return true
}
Pad settings:
Ball settings:
Some updates
I have tried some messages in update function, then encountered with same case ball goes outside from left side of the device (using iPhone 6S)
2016-12-08 14:27:54.436485 Pong[14261:3102941] fatal error: ball out of left bounds: file
You're pinching the ball against the wall, with the enemy. This means that the force is eventually enough to create enough speed of ball movement/force to overcome the physics system, so it pops through the wall. If you make your enemy stop before it pinces the ball against the wall, you should be fine.
This 'pincing' is occurring because of this line of code:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
This is making the enemy chase the ball, which is a good idea for a ball game, but for the way it's being moved is wrong. Using an Action means the enemy has infinite force applied to it, and is aiming for the middle of the ball.
So when the ball gets to the wall, it's stopped against a physics object with infinite static force, then this enemy comes along and applies infinite force from the other side... and the ball either pops inside the bounds of the enemy, or over the other side of the wall, because it's being crushed by infinite forces.
So you either need to take very good care of how you control the enemy with Actions, or use forces to control the enemy, as these won't be infinite, and the physics system will be able to push back on the enemy.
How easy is it to reproduce the problem? In update(), print the ball's position to see where it is when it has 'disappeared'. (this will produce a lot of output, so be warned).
From what you've posted, it doesn't look like the ball is set to collide with the border, meaning the ball will not react (i.e. bounce off) the border and the border itself is immobile (as it's an edge-based physics body). This, combined with a high ball velocity (from a hard hit) might make it possible that you have hit the ball so hard with the 'main' sprite that it's gone through the border - using preciseCollisionDetection=true might resolve this but give the border a category first and add this to the ball's collisionBitMask.
here is an example of what Steve is saying (in your .update())
if ball.position.x > frame.maxX { fatalError(" ball out of right bounds") }
if ball.position.x < frame.minX { fatalError(" ball out of left bounds") }
if ball.position.y > frame.maxY { fatalError(" ball out of top bounds") }
if ball.position.y < frame.minY { fatalError(" ball out of bottom bounds) }
you could also just spam your debug window:
print(ball.position)
This will help you to find out what is going on--if your ball is flying through the boundary, or if it's getting destroyed somewhere, or some other possible bug.
As a workaround (for now) I would just replace the above "fatalError" with "ball.position = CGPoint(x: 0, y: 0)" or some other position to "reset" the ball in case of it getting lost.
You could even store it's last position in a variable, then restore it to that should the above if-statements trigger.
var lastBallLocation = CGPoint(x: 0, y: 0) // Just to initialize
override func update( prams ) {
if ball.position.x > frame.maxX { ball.position = lastBallLocation }
// .. copy the other three cases
lastBallLocation = ball.position // update only on successful position
Or, you could try making the walls thicker (use a shape node or spritenode and lay them on the outside of the frame such as the walls of a house, and your view on screen is the "room")
each wall also has a physics body for bouncing:
I'm trying to get a simple SKSpriteNode to perfectly orbit an SKFieldNode.radialGravityField() once it comes into its defined SKRegion. I have the gravity of my scene set to zero via self.physicsWorld.gravity = CGVectorMake(0.0, 0.0).
I am trying to simulate real space. I have heard about using a joint, but that doesn't seem to be as smooth feeling as using real gravity calculations. Any ideas here?
EDIT: My Code so far does not work as desired. It will send an object into a radialGravityField() but does not orbit it perfectly (or even close to perfect). It does orbit, but in a wild ellipse. And when I say perfectly, I mean in the shape of a circle around the gravityField's center.
ADDITION: I was just thinking of an alternative to using a radialGravityField(). Maybe it would be easier to just calculate the position of the flying object in the update method. If it's position is within a planet's radius, then use an SKJoint and have it orbit. Anybody done that before?
Thank you in advance!
Here is my code so far...
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0)
self.view?.backgroundColor = UIColor.darkGrayColor()
let circle = SKShapeNode(circleOfRadius: 30.0)
circle.position = CGPoint(x: self.frame.width / 2 + 10, y: self.frame.height / 2)
circle.fillColor = .whiteColor()
addChild(circle)
let gravityField = SKFieldNode.radialGravityField()
gravityField.position = circle.position
gravityField.region = SKRegion(radius: 100.0)
gravityField.strength = 4.0
gravityField.enabled = true
addChild(gravityField)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let p = SKSpriteNode(color: UIColor.purpleColor(), size: CGSize(width: 20, height: 20))
p.physicsBody = SKPhysicsBody(rectangleOfSize: p.size)
p.physicsBody?.dynamic = true
p.physicsBody?.mass = 0.5
p.position = touches.first!.locationInNode(self)
addChild(p)
p.physicsBody?.applyImpulse(CGVector(dx: 0.0, dy: 300 * p.physicsBody!.mass))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Your object is in an ellipse because it's velocity does not perfectly match the gravity at that distance. If it initially shoots away from the gravity field then it is going too fast for a circular orbit, try reducing its velocity. If it initially falls towards the gravity field then it is going too slow, so try increasing the velocity.
Alternatively you could adjust the initial distance from the field or the strength of the gravity field.
It should be possible to make the orbit circular, but it will take a lot of fiddling around and should anything perturb the orbit (e.g. a collision) then that will throw it out again.
This is more about the physics of orbital dynamics than programming. Whether gravity is the right solution depends on what you are trying to achieve in the final app. Perhaps this SO question has some useful info.
I want to create my player on top of the background. Somehow this doesn't work. The node counter goes up by one but it isn't visible. I do add the player after I add the background so it should be on top. The code I use to create the player:
var player = SKSpriteNode()
func addPlayer(gameScene: GameScene, xPos: CGFloat, yPos: CGFloat){
player = SKSpriteNode(imageNamed: "player")
player.size = CGSize(width: fieldsWidth, height: fieldsWidth)
player.position = CGPoint(x: xPos, y: yPos)
gameScene.addChild(player)
}
And I access the function from another class like this:
person().addPlayer(self, xPos: fieldsWidth/2, yPos: fieldsWidth/2)
Hopefully you can help me.
If you are sure that you have added node at desired position, which is probably the situation here because node count is incremented, you can set player's and background's zPosition to make player above the background:
var player = SKSpriteNode()
//1. somewhere in your code set background's zPosition to 1 before you add it to the scene
func addPlayer(gameScene: GameScene, xPos: CGFloat, yPos: CGFloat){
player = SKSpriteNode(imageNamed: "player")
//2. Set player's zPosition to be higher than background's zPosition
player.zPosition = 2
player.size = CGSize(width: fieldsWidth, height: fieldsWidth)
player.position = CGPoint(x: xPos, y: yPos)
gameScene.addChild(player)
}
About ignoresSiblingOrder...I would recommend you to leave that to true because it can help a lot when it comes to performance . The only "thing" about this kind of optimization is that you have to explicitly set zPosition for nodes at same zPosition which can overlap, but you don't want to let them overlap in random order (which ignoresSiblingsOrder does when set to true), but rather to overlap in determined order (controlled by you). This is from docs:
When this property is set to YES, the position of the nodes in the
tree is ignored when determining the rendering order. The rendering
order of nodes at the same z position is arbitrary and may change
every time a new frame is rendered.
So, just set zPosition explicitly and you will be fine.
In the View Controller of your scene, set skView.ignoresSiblingOrder = false.
I am trying to create a impenetrable border at the edge of the game scene frame.
In Swift I have attempted to re-write this as:
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 0,y: 100,width: self.frame.size.width,height: self.frame.size.height-200))
}
Instead of giving me a clear boundary at the edge of frame it is providing a weak border, which my player (with no specified mass) can pass over.
I'm also not sure why I am having to subtract and add to the y axis to get the correct border but this is less of a concern. Any help would be much appreciated.
The key is to move your sprites by applying a force/impulse or by setting the velocity property so that they respond correctly to
collisions with other physics bodies (such as an edge loop)
forces such as linear/angular damping, friction, and force fields.
You should first define a bitmask category to represent the physics body of your game scene. I typically use the 32-bit binary value 00000000000000000000000000000001, which I define as a constant:
let SCENE_EDGE_CATEGORY: UInt32 = 0x1
With that constant defined, you can now define the scene's physics body in a very readable way:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody!.categoryBitMask = SCENE_EDGE_CATEGORY
For any sprite that you want to be constrained by the edge of the scene, set its physics body's collision bitmask to the scene's category bitmask. You're telling Sprite Kit that you want the physics engine to handle collisions between that sprite and the scene's edge:
someSprite.physicsBody!.collisionBitMask = SCENE_EDGE_CATEGORY
Hope this helps!
Try this:
override init(size: CGSize)
{
super.init(size: size)
physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0, 0, size.width, size.height))
anchorPoint = CGPointMake(0.0, 0.0)
}
works fine for me.
This will definitely help you to define the edge of the screen in Swift.
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)