Swift - Increase speed of a moving Node using SpriteKit - ios

I am working on a game similar to pong, while starting the game I apply impulse to the ball in random direction, which is at the centre of the screen e.g.
func impulse(){
let randomNum:UInt32 = arc4random_uniform(200)
let someInt:Int = Int(randomNum)
//Ball Impulse
if someInt<49{
ball.physicsBody?.applyImpulse(CGVector(dx: ballSpeed+3, dy: ballSpeed-5))
}else if someInt<99{
ball.physicsBody?.applyImpulse(CGVector(dx: ballSpeed+5, dy: -ballSpeed+5))
}else if someInt<149{
ball.physicsBody?.applyImpulse(CGVector(dx: -ballSpeed-5, dy: -ballSpeed+5))
}else if someInt<200{
ball.physicsBody?.applyImpulse(CGVector(dx: -ballSpeed-3, dy: ballSpeed-5))
}
}
Where ballSpeed is the preset speed of ball.
So is there any way I can gradually increase the velocity of ball with duration while it is in motion? As ball will keep on bouncing around the screen so it is difficult to apply force to it using dx and dy.
EDIT -
I am using above method just to assign a random quadrant/direction to ball at start of the game.
Start, when impulse method is implemented e.g.
Game start image
And I want to increase speed of the ball by a predefined unit over time during gameplay e.g.
Gameplay

Ok, here you go! The ball CONSTANTLY gets faster, no matter what!!! Adjust amount to determine how much the ball gains speed each frame.
class GameScene: SKScene, SKPhysicsContactDelegate {
let ball = SKShapeNode(circleOfRadius: 20)
var initialDY = CGFloat(0)
var initialDX = CGFloat(0)
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsWorld.gravity = CGVector.zero
// Configure ball pb:
let pb = SKPhysicsBody(circleOfRadius: 20)
ball.physicsBody = pb
addChild(ball)
pb.applyImpulse(CGVector(dx: 10, dy: 10))
}
override func update(_ currentTime: TimeInterval) {
initialDX = abs(ball.physicsBody!.velocity.dx)
initialDY = abs(ball.physicsBody!.velocity.dy)
}
override func didSimulatePhysics() {
guard let pb = ball.physicsBody else { fatalError() }
// decrease this to adjust the amount of speed gained each frame :)
let amount = CGFloat(5)
// When bouncing off a wall, speed decreases... this corrects that to _increase speed_ off bounces.
if abs(pb.velocity.dx) < abs(initialDX) {
if pb.velocity.dx < 0 { pb.velocity.dx = -initialDX - amount }
else if pb.velocity.dx > 0 { pb.velocity.dx = initialDX + amount }
}
if abs(pb.velocity.dy) < abs(initialDY) {
if pb.velocity.dy < 0 { pb.velocity.dy = -initialDY - amount }
else if pb.velocity.dy > 0 { pb.velocity.dy = initialDY + amount }
}
}
}
you can modify this to only increase speed every 5 seconds with an SKAction.repeatForever(.sequence([.wait(forDuration: TimeInterval, .run( { code } ))) but IMO having it constantly gain speed is a bit more awesome and easier to implement.

Related

How to detect when an SKSpriteNode hits another SKSpriteNode?

I'm trying to make a game where you tilt your phone and try to keep the ball inside the boundary. I can't seem to figure out how to make the ball not go through the boundary. I have the tilt to move the ball working, but it just goes through all my boundaries and I can't seem to figure out how to make the ball stop when it comes in contact with a boundary. Here is my code:
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody = border
boundary = (self.childNode(withName: "boundry") as! SKSpriteNode) //the boundary is spelled wrong
airplane = SKSpriteNode(imageNamed: "ball image")
airplane.physicsBody = SKPhysicsBody(circleOfRadius: 10)
airplane.position = CGPoint(x: -211.163, y: 367.3)
airplane.size = CGSize(width: 50, height: 50)
airplane.physicsBody?.isDynamic = true
airplane.physicsBody?.affectedByGravity = false
airplane.physicsBody?.allowsRotation = true
airplane.physicsBody?.pinned = false
self.addChild(airplane)
if motionManager.isAccelerometerAvailable {
// 2
motionManager.accelerometerUpdateInterval = 0.01
motionManager.startAccelerometerUpdates(to: .main) {
(data, error) in
guard let data = data, error == nil else {
return
}
// 3
let currentX = self.airplane.position.x
self.destX = currentX + CGFloat(data.acceleration.x * 500)
let currentY = self.airplane.position.y
self.destY = currentY + CGFloat(data.acceleration.y * 500)
}
}
}
override func update(_ currentTime: TimeInterval) {
let action = SKAction.moveTo(x: destX, duration: 1)
let action2 = SKAction.moveTo(y: destY, duration: 1)
airplane.run(action)
airplane.run(action2)
}
In SpriteKit things don't collide unless you give them a set of matching collisionBitMask. ie border.collisionBitMask & airplane.collisionBitMask need to have at least one non zero bit in common. Try setting both to 1 to begin with.
Another thing is that you may need to add a distance constraint to airplane so that it cannot escape from the border if speed is too high.

How to move sprite just left or right using CoreMotion?

For this game I am creating, I want the Sprite "Character" to either move left or right by me tilting the device either left or right.
I have looked into it and changed the code, but it seems like that it does not work.
When I run it, the "Character" moves in only one direction and still goes either up or down.
I would just like the "Character" to move only left or right, not up or down.
Any help on this?
Here is a look at the GameScene.swift file:
import SpriteKit
import CoreMotion
class GameScene: SKScene {
let motionManager = CMMotionManager()
var Ground = SKSpriteNode()
var Character = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
Character = SKSpriteNode(imageNamed: "NinjaGhost")
Character.physicsBody = SKPhysicsBody(circleOfRadius: Character.size.height)
Character.size = CGSize(width: 200, height: 200)
Character.physicsBody?.allowsRotation = false
Character.zPosition = 2
Character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Character)
Ground = SKSpriteNode(imageNamed: "Dirt Background")
Ground.size = CGSize(width: 1920, height: 1080)
Ground.zPosition = 1
Ground.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Ground)
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue() ) {
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * 1, 0)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You should follow the swift guidelines, your properties should not start with capital letters, only classes, structs, enums and protocols should. It makes code harder to read on stack overflow (code is marked blue but shouldn't) and in general its not good practice.
Change your code to this because there was a few errors.
character = SKSpriteNode(imageNamed: "NinjaGhost")
character.size = CGSize(width: 200, height: 200) // Better to call this before positioning
character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2) // Give pos before physics body
character.physicsBody = SKPhysicsBody(circleOfRadius: character.size.width / 2) // You have to divide by 2 when using circleOfRadius to get proper size in relation to the sprite
character.physicsBody?.allowsRotation = false
character.physicsBody?.affectedByGravity = false // Stop falling
character.zPosition = 2
self.addChild(character)
In your motion queue code you are manipulating the scene itself, so that will not work.
Try this code instead which uses the physics body to move the sprite. This is my preferred way of doing it because it gives the sprite more natural movement, especially when changing direction, compared to directly manipulating the sprites position property (If you want to manipulate the sprite position property directly than read this article. http://www.ioscreator.com/tutorials/move-sprites-accelerometer-spritekit-swift)
if let error = error { // Might as well handle the optional error as well
print(error.localizedDescription)
return
}
guard let data = motionManager.accelerometerData else { return }
if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft {
character.physicsBody?.applyForce(CGVectorMake(-100 * CGFloat(data.acceleration.y), 0))
} else {
character.physicsBody?.applyForce(CGVectorMake(100 * CGFloat(data.acceleration.y), 0))
}
This will make the sprite move on the x axis. I am using data.acceleration.y because in this example the game is in landscape so you have to use the Y data to move it on the x axis.
I am also checking in which landscape orientation the device is so that the motion works in both orientations.
Adjust the 100 here to get the desired force depending on the size of the sprite (higher is more force). Big sprites properly need a few 1000s to move.

Balancing a sprite

So my problem regards the rotation of a sprite. I want it look like it's balancing. When you tap left - the sprite moves left, when you tap right - the sprite moves right. To get a better idea, put a pencil on it's end and try balance it... Yeah... that (but only in x axis).
Currently I am rotating a sprite either clockwise or anti-clockwise dependent upon whether I tap the left or right side of the screen. This is all done using SKActions.
The problem with this is that it results in a very 'jerky' and not particularly realistic motion.
I assume that I want to use physics body and something similar to velocity but my questions are:
the use of velocity is right... isn't it?
would i better off with a volume based sprite (and get it to take into account it's volume and mass) or just a simple edge based sprite?
Thanks in advance!
-- Code -
This is how I'm currently rotating and moving my sprite:
import SpriteKit
enum rotationDirection{
case clockwise
case counterClockwise
case none
}
// Creates GameScene and initialises Sprites in Scene //
class GameScene: SKScene, SKPhysicsContactDelegate {
// Rotation direction variable for ship motion (rotation and movement) //
var currentRotationDirection = rotationDirection.none
var xVelocity: CGFloat = 0
var sprite = SKSpriteNode()
// Setup Scene here //
override func didMoveToView(view: SKView) {
// Background colour //
self.backgroundColor = SKColor.whiteColor()
// sprite Physics + add's sprite //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.affectedByGravity = true
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addSprite()
// Initialises sprite node and it's properties //
func addSprite() {
// Sprite dimension properities //
sprite.name = "sprite"
sprite = SKSpriteNode(imageNamed: "sprite")
sprite.setScale(0.5)
sprite.position = CGPointMake(frame.midX, 220)
sprite.anchorPoint = CGPoint(x: 0.5, y: 0.25)
sprite.zPosition = 1;
// sprite Physics properties //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.categoryBitMask = UInt32(shipCategory)
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
sprite.physicsBody?.collisionBitMask = 0
self.addChild(sprite)
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Defines UI Touch //
let touch = touches.first as UITouch!
let touchPosition = touch.locationInNode(self)
// Sets up Inital Rotation Direction //
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
// Left or Right movement based on touch //
if touchPosition.x < CGRectGetMidX(self.frame) {xVelocity = -75}
else {xVelocity = 75}
// Clockwise or anticlockwise rotation based on touch //
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
reverseRotation()
currentRotationDirection = newRotationDirection
}
else if (currentRotationDirection == .none) {
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation() {
let oldRotateAction = sprite.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction!)
sprite.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation() {
sprite.removeActionForKey("rotate")
}
func setupRotationWith(direction direction: rotationDirection){
let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 2)
let repeatAction = SKAction.repeatActionForever(rotate)
sprite.runAction(repeatAction, withKey: "rotate")
}
override func update(currentTime: CFTimeInterval) {
let rate: CGFloat = 0.3; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody!.velocity.dx, dy:0);
sprite.physicsBody!.velocity=CGVector(dx:sprite.physicsBody!.velocity.dx+relativeVelocity.dx*rate, dy:0);
}
I think what your are going to want to look into is acceleration. Right now you are adjusting verlocityX with a static amount using:
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity = -75
}
else {
xVelocity = 75
}
Doing this will result in your jerky motion. Which is actually just linear. Using acceleration you can avoid this.
For this you'll need two more variables for maxVelocity and acceleration. You want a maxVelocity probably to limit the velocity. Each frame you'll need to increase the velocity with the acceleration amount.
I suggest you try something like this:
velocity = 0 // No movement
acceleration = 2 // Increase velocity with 2 each frame
maxVelocity = 75 // The maximum velocity
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity += acceleration
if xVelocity <= -maxVelocity {
xVelocity = -maxVelocity
}
}
else {
xVelocity -= acceleration
if xVelocity >= maxSpeed {
xVelocity = maxSpeed
}
}
Increasing your velocity like this will result with a rotation speed of 2 in the first frame, than 4 in the second and 6 in the thirds etc. So it will have an increasing effect. While at the same time, when trying to reverse it you wont have a jerky effect either. Say the velocity is at 50, you first decrease it to 48, 46, 44, etc. Not until the 0 point you will actually start rotating the other way.

Simulate universal gravitation for two Sprite Kit nodes

I have two nodes in Sprite Kit, and I'm writing in Swift.
How would I make the nodes attracted to each other by the force of gravity? Also, how would I use their masses? If an object has a big mass, it should gravitate more.
You can loop through all nodes and calculate the impulse to all other nodes using the appropriate ratios of the universal gravitation equation. I just wrote a quick example showing how this is done. You can make your own custom "mass" factor, however I'm simply using Sprite Kit's. I also added a strength factor to amplify the impulse. I'm also assuming fixed time step of 1/60 seconds.
class GameScene: SKScene {
var nodes: [SKShapeNode] = []
let dt: CGFloat = 1.0/60.0 //Delta time.
let radiusLowerBound: CGFloat = 1.0 //Minimum radius between nodes check.
let strength: CGFloat = 10000 //Make gravity less weak and more fun!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVector()
for i in 1 ... 50 { //Create 50 random nodes.
let rndRadius = 15 + CGFloat(arc4random_uniform(20))
let rndPosition = CGPoint(x: CGFloat(arc4random_uniform(UInt32(self.size.width))), y: CGFloat(arc4random_uniform(UInt32(self.size.height))))
let node = SKShapeNode(circleOfRadius: rndRadius)
node.position = rndPosition
node.physicsBody = SKPhysicsBody(circleOfRadius: rndRadius)
self.addChild(node)
nodes.append(node)
}
}
override func update(currentTime: NSTimeInterval) {
for node1 in nodes {
for node2 in nodes {
let m1 = node1.physicsBody!.mass*strength
let m2 = node2.physicsBody!.mass*strength
let disp = CGVector(dx: node2.position.x-node1.position.x, dy: node2.position.y-node1.position.y)
let radius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)
if radius < radiusLowerBound { //Radius lower-bound.
continue
}
let force = (m1*m2)/(radius*radius);
let normal = CGVector(dx: disp.dx/radius, dy: disp.dy/radius)
let impulse = CGVector(dx: normal.dx*force*dt, dy: normal.dy*force*dt)
node1.physicsBody!.velocity = CGVector(dx: node1.physicsBody!.velocity.dx + impulse.dx, dy: node1.physicsBody!.velocity.dy + impulse.dy)
}
}
}
}
Instead of performing the calculation manually you could also add field nodes to your physics bodies to simulate the effect. Although be warned, field nodes are broken in certain versions of Sprite Kit.

Centering the camera on the character

I am a beginner and I have started my first project. It should be a little game where you can run to the left and to the right. But I don't know how to set the camera settings.
I'd be very thankful if you could help me, tell me how I can improve my code and what is wrong.
Here is my Code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground:SKSpriteNode = SKSpriteNode()
var hero: MPCharacter!
var tree:MPTree!
override func didMoveToView(view: SKView) {
backgroundColor = UIColor(red: 155.0/255.0, green: 201.0/255.0, blue: 244.0/255.0, alpha: 1.0)
self.anchorPoint = CGPointMake(0.5, 0.5)
// --> hero <--
hero = MPCharacter()
hero.position = CGPointMake(frame.size.width/2, view.frame.size.height/2)
hero.zPosition = 100
hero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(hero.size.width, hero.size.height))
hero.physicsBody!.dynamic = true
addChild(hero)
// --> ground <--
ground = SKSpriteNode(imageNamed: "ground")
ground.size = CGSizeMake(frame.size.width, frame.size.height/3)
ground.position = CGPointMake(frame.size.width/2, frame.size.height/2 - ground.size.height)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(frame.size.width, ground.size.height - 25))
ground.physicsBody!.dynamic = false
addChild(ground)
// --> Tree <--
tree = MPTree()
tree.position = CGPointMake(frame.size.width/2, ground.size.height + tree.size.height/3 - 10)
tree.zPosition = 10
tree.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(tree.size.width, tree.size.height - 10))
tree.physicsBody!.dynamic = false
addChild(tree)
// --> Physics <--
self.physicsWorld.gravity = CGVectorMake(0, -5.0)
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(-5, 10))
}
else {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(5, 10))
}
}
}
}
Im not quite sure what you are trying to achieve. One way to make your character 'run' to the left (or right) is by moving a background (and other game objects) to the left when the character is running right (and vice versa). This way your character will always be in the center of the screen, because he is not really moving, you create an 'illusion' that he is moving in your gameworld. Giving your character a run animation will make it look even better...
There is a tutorial on the website of Ray Wenderlich which also covers 'parallax scrolling':
http://www.raywenderlich.com/49625/sprite-kit-tutorial-space-shooter
Hope this helps..
If you're trying to center the camera on the player, Apple Docs provide you a nice example for how to do so over here (I would add code, but the best way to learn is to get your hands dirty). You might have to translate the methods to swift, but it is the general implementation for what you wish to do. The problem with the answer above is that if you would move the background, you would have to calculate the forces and the impulses directly and apply the inverse vectors to the background. This might require more processing than with simply moving the camera. Hope this helps!!

Resources