I'm making a simple game(using Swift & SpriteKit), where I have a circle that can be dragged around. but the circle is not allowed to go through walls.
My collision BitMask works perfectly, but when I drag fast enough, the circle ends up going through the walls.
The Initialisation of the Player Sprite goes Like this:
func initPlayerSprite(){
let playerTexture = SKTexture(imageNamed: "player.png")
let originX = CGRectGetMidX(self.frame)
let originY = CGRectGetMidY(self.frame)
player = SKSpriteNode(texture: playerTexture, size: CGSize(width: 26, height: 26))
player.position = CGPoint(x: originX , y: originY)
player.physicsBody = SKPhysicsBody(circleOfRadius: playerTexture.size().height/2)
player.physicsBody!.dynamic = true
player.physicsBody?.allowsRotation = false
player.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
player.physicsBody!.contactTestBitMask = ColliderType.Floor.rawValue + ColliderType.Gap.rawValue
player.physicsBody!.collisionBitMask = ColliderType.Wall.rawValue + ColliderType.Floor.rawValue
self.addChild(player)
}
My code for moving the Sprite goes like this:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){
var nodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let dx = -player.position.x + location.x
let dy = -player.position.y + location.y
let movePlayer = SKAction.moveBy(CGVector(dx: dx, dy: dy), duration: 0.02)
}
}
Any Idea how to make sure the collision detection work even at high velocities?
From your code its hard to judge.
Why are you calling the collisions like
ColliderType.Wall.rawValue + ColliderType....
I haven't seen this before with a "+" as a separator, usually you use a "|"
to separate them.
ColliderType.Wall.rawValue | ColliderType....
Using a plus should add the two values together and will not treat it as 2 collision types, as far as I understand. I might be wrong here.
Also did you try using precise collision detection like so?
...physicsBody.usesPreciseCollisionDetection = true.
Apple describes this bool as follows
The default value is NO. If two bodies in a collision do not perform precise collision detection, and one passes completely through the other in a single frame, no collision is detected. If this property is set to YES on either body, the simulation performs a more precise and more expensive calculation to detect these collisions. This property should be set to YES on small, fast moving bodies.
Not sure this is helping
Related
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
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.
So I have a game where you need to shoot enemies. When touchesBegan a bullet comes under your finger, when touchesEnded – it fires at enemy. I made it with SKActions. It's working well until it's game over. I don't have a special scene for it, it's just a node with buttons. But when it appears, SKActions on bullet and enemies are still running by touch. I want to disable them when it's game over and don't know how to do it. For example, one of my enemis I created like this:
func addMiddleHeart() {
middleheart = SKSpriteNode(imageNamed: "redh")
middleheart.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame) + 100)
middleheart.zPosition = 1
middleheart.physicsBody = SKPhysicsBody(circleOfRadius: middleheart.size.width / 2)
middleheart.physicsBody?.dynamic = true
middleheart.physicsBody?.categoryBitMask = PhysicsCategory.MiddleHeart
middleheart.physicsBody?.contactTestBitMask = PhysicsCategory.Arrow
middleheart.physicsBody?.collisionBitMask = PhysicsCategory.None
addChild(middleheart)
let moveToPoint = SKAction.moveTo(CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) + leftHeart.size.height * 1.5), duration: 0.5)
SKActionTimingMode.EaseOut
middleheart.runAction(moveToPoint)
}
override func didMoveToView(view: SKView) {
runAction(SKAction.runBlock(addMiddleHeart))
}
gameScene.removeAllActions() is the statement that you want to use. It'll indiscriminately remove all running SKActions.
The movement of a physics body circle is too erratic for what I want to achieve. I would like to restrict it so it follows a certain path touching specific points (or a range of points) as shown in the image below. How can I set the physics properties to traverse a similar path?
how to set physics properties for a circle so it follows given path
So essentially you are looking to move a node to a particular point using real-time motion. I have an answer here showing how to do this, however given the number of up votes this question has received, I will provide a more detailed answer.
What the answer I linked to doesn't provide is traversing a path of points. So below I have provided a solution showing how this can be done below. It simply just moves to each point in the path, and each time the node reaches a point, we increment the index to move to the next point. I also added a few variables for travel speed, rate (to make the motion more smooth or static) and whether or not the node should repeat the path. You could further expand upon this solution to better meet the needs of your game. I would definitely consider subclassing a node and building this behavior into it so you can re-use this motion for multiple nodes.
One final note, you may notice the calculation for the impulse varies between my solution below and the answer I linked to above. This is because I am avoiding using angle calculation because they are very expensive. Instead I am calculating a normal so that the calculation is more computationally efficient.
One final note, my answer here explains the use of the rate factor to smooth the motion and leave room for motion distortions.
import SpriteKit
class GameScene: SKScene {
var node: SKShapeNode! //The node.
let path: [CGPoint] = [CGPoint(x: 100, y: 100),CGPoint(x: 100, y: 300),CGPoint(x: 300, y: 300),CGPoint(x: 300, y: 100)] //The path of points to travel.
let repeats: Bool = true //Whether to repeat the path.
var pathIndex = 0 //The index of the current point to travel.
let pointRadius: CGFloat = 10 //How close the node must be to reach the destination point.
let travelSpeed: CGFloat = 200 //Speed the node will travel at.
let rate: CGFloat = 0.5 //Motion smoothing.
override func didMoveToView(view: SKView) {
node = SKShapeNode(circleOfRadius: 10)
node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
node.physicsBody!.affectedByGravity = false
self.addChild(node)
}
final func didReachPoint() {
//We reached a point!
pathIndex++
if pathIndex >= path.count && repeats {
pathIndex = 0
}
}
override func update(currentTime: NSTimeInterval) {
if pathIndex >= 0 && pathIndex < path.count {
let destination = path[pathIndex]
let displacement = CGVector(dx: destination.x-node.position.x, dy: destination.y-node.position.y)
let radius = sqrt(displacement.dx*displacement.dx+displacement.dy*displacement.dy)
let normal = CGVector(dx: displacement.dx/radius, dy: displacement.dy/radius)
let impulse = CGVector(dx: normal.dx*travelSpeed, dy: normal.dy*travelSpeed)
let relativeVelocity = CGVector(dx:impulse.dx-node.physicsBody!.velocity.dx, dy:impulse.dy-node.physicsBody!.velocity.dy);
node.physicsBody!.velocity=CGVectorMake(node.physicsBody!.velocity.dx+relativeVelocity.dx*rate, node.physicsBody!.velocity.dy+relativeVelocity.dy*rate);
if radius < pointRadius {
didReachPoint()
}
}
}
}
I did this pretty quickly so I apologize if there is a mistake. I don't have time now but I will add a gif showing the solution later.
A note about collisions
To fix the erratic movement during a collision, after the 2 bodies collide set the "rate" property to 0 or preferably a very low number to reduce the travel velocity impulse which will give you more room for motion distortion. Then at some point in the future (maybe some time after the collision occurs or preferably when the body is moving slow again) set the rate back to its initial value. If you really want a nice effect, you can actually ramp up the rate value over time from 0 to the initial value to give yourself a smooth and gradual acceleration.
Quick implementation using followPath:duration:
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor.blackColor()
let xWidth: CGFloat = CGRectGetMaxX(self.frame)
let yHeight: CGFloat = CGRectGetMaxY(self.frame)
let ball = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(50.0, 50.0))
let offset : CGFloat = ball.size.width / 2
// Moving path
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, offset, yHeight / 2)
CGPathAddLineToPoint(path, nil, xWidth * 2 / 3, yHeight - offset)
CGPathAddLineToPoint(path, nil, xWidth - offset, yHeight * 2 / 3)
CGPathAddLineToPoint(path, nil, xWidth / 2, offset)
CGPathAddLineToPoint(path, nil, offset, yHeight / 2)
// Movement
let moveByPath = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: 4.0)
let moveForever = SKAction.repeatActionForever(moveByPath)
ball.runAction(moveForever)
self.addChild(ball)
}
In GameViewController.swift, I changed the default GameScene.unarchiveFromFile method to let scene = GameScene(size: view.bounds.size) for creating the scene's size.
Preview:
I have a sprite node that spawns in via a function:
let NBrick1 = SKSpriteNode(texture: Brick1)
NBrick1.setScale(0.04)
NBrick1.position = CGPointMake(0.0, CGFloat(y1 + bounds/2))
NBrick1.physicsBody = SKPhysicsBody(rectangleOfSize: NBrick1.size)
NBrick1.physicsBody?.dynamic = true
BrickPairs.addChild(NBrick1)
I am using this SKAction to move the bricks:
let moveBricks = SKAction.moveByX(-distanceToMove, y: 0.0, duration: NSTimeInterval(0.01 * distanceToMove))
let removeBricks = SKAction.removeFromParent()
BricksMoveAndRemove = SKAction.sequence([moveBricks,removeBricks])
I was wondering if there was a way to make the brick move faster as time goes on.
Its actually quite simply and you do not need to get involved with SKActions. The code will below will speed up the movement of a sprite, or multiple sprites, (if you apply the same code.) What it is doing is changing the position of the sprite every frame by a little bit more each time. This is the moveFactor variable. To change the start speed and rate that it speeds up, change the value that I put in.
var moveFactor:CGFloat = 0.5
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
sprite.position = CGPoint(x: sprite.position.x + moveFactor, y: sprite.position.y)
moveFactor *= 1.005
}