How to make speed of sprite same? - swift

I started writing a simple game to improve my SpriteKit skills in the Xcode using swift language.
In my game there is just one sprite which change it position on place where I clicked on screen.
I did it like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
ZombieNode.run (SKAction.move(to: CGPoint(x: location.x, y: location.y), duration: 1))
}
}
But there is just one problem, the velocity of my sprite depends on where I clicked. So I understand why - because sprite should change location in one second so if i click far from my sprite the speed will be faster, if i click near to sprite the speed is very low. So my question is - How to do speed same? Maybe I should use other function instead of
ZombieNode.run (SKAction.move(to: CGPoint(x: location.x, y: location.y), duration: 1))
or maybe there are some math formulas to do this. So help me please, how can I make speed same everywhere?

You would need to do a distance check first, then multiply your duration by a distance factor.
Using the pythagorean theorem of x2 + y2 = distance2
let deltaX = location.x - current.x
let deltaY = location.y - current.y
let distance = (deltaX * deltaX + deltaY * deltaY).squareRoot()
Then you can get a ratio, using the distance you'd like to travel in a second. For example, if you'd like to move at 400 pixels per second you could do the following:
let pixelsPerSecond = 400
let timeToTravel = distance/pixelsPerSecond
Then use the time to travel here:
ZombieNode.run (SKAction.move(to: CGPoint(x: location.x, y: location.y), duration: timeToTravel))

Related

Swift: Calculate how much to rotate an SKSpriteNode to have it pointing at a CGPoint

I'm making a SpriteKit game in Playgrounds based on shooting space rocks. In the game, there is a gun, which is a SKSpriteNode and I want it to turn to the point that I pass into a function (CGPoint). What I would like to know is how would I calculate how much to turn the gun to face the given point? Thanks in advance!
Long ago, after a lot of mind tweaking math and a sleepless night, I came up with this function:
func gunAngle(gunPosition: CGPoint, targetPosition: CGPoint) -> CGFloat {
let deltaX = Float(targetPosition.x - gunPosition.x)
let deltaY = Float(targetPosition.y - gunPosition.y)
let pi = CGFloat(M_PI)
let angle = CGFloat(atan2f(deltaY, deltaX))
var newAngle = angle
if angle < (-pi / 2) {
newAngle += 2 * pi
}
return newAngle - pi/2 // Subtracting 90 degrees out of the resulting angle, as in SpriteKit 0 degrees faces left, unless you rotate your gun in the sprite accordingly
}
I realize this may not be the best method but it works for me. Some math gurus could probably come up with something really brilliant here. I'm not yet any of those.
Thanks to Ray Wenderlich, on his website there is a tutorial on that topic that helped me a lot in putting the foundation of that math.
Maybe,something like
func aim(node:SKSpriteNode, point:CGPoint){
let angle = atan2(point.y - node.position.y, point.x - node.position.x)
node.run(SKAction.rotate(byAngle: angle, duration: 0.5))
}
An alternative solution (this may not be applicable to you), is to create an SKNode (called target perhaps) and then to set up an SKContraint so that your gun always faces the target. You can then move target when required to wherever you want the gun to face and the gun will turn accordingly.
let gun = SKNSpritenode...
let target = SKNode...
let orientRange = SKRange(lowerLimit: 0.0, upperLimit: 0.0)
let orientConstraint = SKConstraint.orientToNode(target, offset: orientRange)
gun.constraints = [orientConstraint]

Swift Sprite Collision. Objects passing Through walls

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

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.

Why do I need to adjust after convert point?

My scene is setup scene > world: sknode > camera: sknode > ship: SKSpriteNode
I want the ship to point to the location of the touch:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touchLocation = touch.locationInNode(self)
let location = cameraNode.convertPoint(touchLocation, toNode: world)
moveCameraNode(CGPointsToVector(location, b: cameraNode.position))
let shipTouchPosition = scene?.convertPoint(touchLocation, fromNode: world)
let shipPosition = scene?.convertPoint(ship.position, fromNode: world)
let angle = atan2(shipTouchPosition!.y - shipPosition!.y, shipTouchPosition!.x - shipPosition!.x)
let magicNumber = CGFloat(M_PI_2)
ship.runAction(SKAction.rotateToAngle(angle - magicNumber, duration: 0.1, shortestUnitArc: true))
}
override func didFinishUpdate() {
centerOnNode(cameraNode)
}
func centerOnNode(node: SKNode) {
let cameraPositionInScene = node.scene?.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPointsSubtract(node.parent!.position, b: cameraPositionInScene!)
}
So, it now works ok but I'm not sure why I need the magicNumber.
EDIT: Thanks to #mundi for catching a stupid error on my part which was obscuring my real question of what I'm doing wrong with convertPoint that I have to adjust it afterwards.
Angles are calculated in radians. 360 degrees are 2 times the constant PI, or 2 * M_PI. In SpriteKit, angle calculations start from center right (equivalent to 3 o'clock), corresponding to 90 degrees or one half PI, M_PI_2.
80 read as radians evaluates to 1.46018 which is very close to M_PI_2 or 1.57079. This is why this number appears to "magically" fix this discrepancy, even though it is slightly off. The true "magic" number is CGFloat(M_PI_2).

how to set physics properties for a circle so it follows given path

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: