Ball shot too fast the first time - swift

I have a sprite that represents a cannon ball, and I shoot it in this method:
func shoot() {
let ball = Ball(radius: 8.0)
ball.position = CGPointMake(size.width / 2.0, 72.0)
self.addChild(ball)
// Explosion is just a simple particle system
let explosion = Explosion()
explosion.position = CGPointMake(size.width / 2.0, 64.0)
self.addChild(explosion)
let force = CGVectorMake(0.0, 20000.0)
ball.physicsBody!.applyForce(force)
}
Ball is a body with mass 1.0, created this way:
init(radius: CGFloat) {
super.init()
let path = UIBezierPath(arcCenter: CGPointZero, radius: radius, startAngle: 0.0, endAngle: 2.0 * PI, clockwise: false)
self.path = path.CGPath
self.strokeColor = SKColor.blackColor()
self.fillColor = SKColor.blackColor()
self.name = "Ball \(self)"
let body = SKPhysicsBody(circleOfRadius: radius)
body.dynamic = true
body.categoryBitMask = BallCategory
body.contactTestBitMask = EnemyCategory
body.collisionBitMask = EnemyCategory
body.mass = 1.0
body.affectedByGravity = false
physicsBody = body
}
The problem is that the first time (and only the first time) that I shoot the ball it's super-fast. All the other times they have a different, lower speed, why?

It looks like you are using applyForce in the wrong way. From the docs :
A force is applied for a length of time based on the amount of
simulation time that passes between when you apply the force and when
the next frame of the simulation is processed. So, to apply a
continuous force to an body, you need to make the appropriate method
calls each time a new frame is processed. Forces are usually used for
continuous effects
Means that you have to call applyForce: inside update: method. And from your code, it can be seen that you are not doing that (shoot() method is probably called inside touchesBegan).
Also from the docs related to applyForce method:
The acceleration is applied for a single simulation step (one frame).
About super-fast ball issue... I have a theory, but can't say for sure whats going on, especially because you are not using applyForce like its meant, which I think is the main problem.
So, here is the theory:
A time difference between each frame can vary, especially when you are starting an app and things are loading for the first time, and because force is multiplied by that time difference you are getting weird results.
On the other hand, applyImpulse: is perfect for your situation. Shooting a ball is an instantaneous action, and applyImpulse is not dependant on the simulation time like applyForce (see quote from docs I've posted).
Solution:
Use applyImpulse to shoot a ball. This is sanctioned way. If you want to move a ball with applyForce, do that through update method in each frame.
Also consider resource preloading (preloading sounds, emitters, textures) before starting an actual gameplay which will save you from fps drops when game starts.
Hope this helps!

Related

Swift: SKEmitterNode affected by PhysicsWorld gravity?

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

Difficulty with orbiting a SKFieldNode in SpriteKit

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.

Swift making sksprite node move faster with time

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
}

Resetting and changing speed on SKActions

Is there a way to change the velocity/speed of a skaction and also to reset the skaction?
let wait = SKAction.waitForDuration(5.0)
let moveRight = SKAction.moveByX(300, y:0, duration: 1.0)
let sequence = SKAction.sequence([wait, moveRight])
let endlessAction = SKAction.repeatActionForever(sequence)
node.runAction(endlessAction)
This code works but the things i would like to change is how fast the SKSpriteNode moves to the right as at the moment it it quite slow and also to make the SKSpriteNode return to its orignal position rather than keep on moving to right forever?
Thank you
Since velocity = distance / time, decreasing the duration will increase the speed the the sprite will move across the screen.
Regarding your second point, by considering how SKAction.moveByX(300, y:0, duration: 1.0) moves the node to the right; SKAction.moveByX(-300, y:0, duration: 1.0) must therefore move the node to the left, back to its original position.
Hope that helps.

Get a list of nodes in an specific area?

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.
This is what I'm trying now:
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.scaleMode = SKSceneScaleMode.AspectFit
self.size = view.bounds.size
// Add world
world = SKShapeNode(rectOfSize: view.bounds.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
player.physicsBody.dynamic = false
player.strokeColor = SKColor.blueColor()
player.fillColor = SKColor.blueColor()
player.position = CGPointMake(90, -50)
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
// Define rect position and size (area that will be evaluated for bodies)
var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)
// Show rect for debug
rShape.removeFromParent()
rShape = SKShapeNode(rect: r)
rShape.strokeColor = SKColor.redColor()
self.addChild(rShape)
// Evaluate rect
rShape.fillColor = SKColor.clearColor()
self.physicsWorld.enumerateBodiesInRect(r) {
(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
}
}
}
This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.
Could this be because this is just beta or am I doing something wrong?
You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.
Think of it like a 'tractor beam' or a 'warp rectangle'.
I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:
self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
// enumerates all nodes in given frame
}
I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.
enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.
I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.
The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.
The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.
I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.
In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.
In order to use enumerateBodiesInRect correctly, I had to do something as follows:
// get your world rect based on game logic
let worldRect = getWorldRect()
// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)
let worldScale = scene.worldNode.xScale // assert this is not 0
// now to get the scene rect relative to the world rect, in scene coordinates
let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)
world.physicsWorld.enumerateBodiesInRect(sceneRect) {
// your code here
}
Hope this helps.
I am not sure if this is a good practice. Correct me if not. But I am using
let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)
I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.
There is only nodesAtPoint: method.
To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.
yes problem may occur because of your player's image, for example try to use 10px smaller body size:
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));