Scaling a node based on position - swift

I was wondering if there is a way to scale a sprite node based on its position?
So for example when a characters y position increases, you want him to appear smaller like he is walking further away from you. And when his y position decreases you want him to appear larger as he is closer to you.
let swipeUp = UISwipeGestureRecognizer()
override func didMoveToView(view: SKView) {
swipeUp.addTarget(self, action: "swipedUp")
swipeUp.direction = .Up
self.view!.addGestureRecognizer(swipeUp)
swipeDown.addTarget(self, action: "swipedDown")
swipeUp.direction = .Down
self.view!.addGestureRecognizer(swipeDown)
addSprite()
}
func swipedUp(){
let amountToMove: CGFloat = 100
let move: SKAction = SKAction.moveByX(0, y: amountToMove, duration: 0.1)
character.runAction(move)
}
func swipedDown(){
let amountToMove: CGFloat = 100
let move: SKAction = SKAction.moveByX(0, y: -amountToMove, duration: 0.1)
character.runAction(move)
}
func addSprite(){
let sprite = SKSpriteNode(imageNamed: "testImage")
sprite.position = CGPoint(x: xPos, y: yPos)
}

override the sprite position property and set the scale factor in it. Here is a very basic example, you will need to determine what your scale factor actually is based on y.
override var position{
didSet{
self.setScale((screen.height - y) / (screen.height / 2))
}
}

Related

Display upperLimit SKRange

So here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ship1 = [2,1]
var ship2 = [1,2]
let jonahSpriteNode = SKSpriteNode(imageNamed: "jonah_spaceship")
let georgeSpriteNode = SKSpriteNode(imageNamed: "george_spaceship")
override func didMove(to view: SKView) {
//var jonahFrames = [SKTexture]()
jonahSpriteNode.position = CGPoint(x: 30, y: frame.midY)
jonahSpriteNode.size = CGSize(width: 100.0, height: 100.0)
addChild(jonahSpriteNode)
georgeSpriteNode.position = CGPoint(x: 628, y: frame.midY)
georgeSpriteNode.size = CGSize(width: 100.0, height: 100.0)
addChild(georgeSpriteNode)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
var touchLocation = touch.location(in: self)
var angle1 = atan2(jonahSpriteNode.position.y - touchLocation.y , jonahSpriteNode.position.x - touchLocation.x)
var angle = angle1 - CGFloat(Double.pi / 1)
makeCircularRange(to: jonahSpriteNode)
let rotate = SKAction.rotate(toAngle: angle, duration: 1.0)
let move = SKAction.move(to: CGPoint(x: touchLocation.x, y: touchLocation.y), duration: 2.5)
let sequence = SKAction.sequence([rotate, move])
jonahSpriteNode.run(sequence)
}
}
func makeCircularRange(to node: SKNode) {
let range = SKRange(lowerLimit: 0, upperLimit: 400)
let constraint = SKConstraint.distance(range, to: .zero)
node.constraints = [constraint]
}
}
I wanted to display the SKRange by showing upperLimit in a certain color. I tried making nodes around the limit and then coloring the nodes but it just showed a bunch of errors. If you have any ideas please answer.
Something like this:
The sprite node will be in the center and the circle will show where it can move.
You can make a circular shape using range.upperLimit as a radius an add to scene.
func drawCircularRangeBy(range: SKRange) {
let radius = range.upperLimit
let node = SKShapeNode(circleOfRadius: radius)
node.strokeColor = .white
addChild(node)
}
Take a look of this example: https://github.com/Maetschl/SpriteKitExamples/blob/master/CircularRange/CircularRange/GameScene.swift
If you really need dots instead of a line please see this answer: Drawing dashed line in Sprite Kit using SKShapeNode

How to create realistic spinning wheel in SpriteKit

I am trying to create a spinning fortune wheel action via SKAction. I have a SKNode which used as wheel, this SKNode is a circle that divided to four quarters (each quarter in different color). also I set an SKAction (which is repeating for 10 counts) that spin the SKNode around fixed point (the node's center). The problem is that the action is running well but it stops suddenly and not slowing down - like a real wheel. I don't really have an idea how to set this animation, I mean to slow the spinning down before the action is stop.
Here is my code so far:
class GameScene: SKScene {
let colors = [SKColor.yellow, SKColor.red, SKColor.blue, SKColor.purple]
override func didMove(to view: SKView) {
createWheel()
let sq = CGRect(x: size.width/2, y: size.height/2, width: 300, height: 300)
let sqx = SKShapeNode(rect: sq)
sqx.lineWidth = 2
sqx.fillColor = .clear
sqx.setScale(1.0)
addChild(sqx)
}
func createWheel() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: -200))
path.addArc(withCenter: CGPoint.zero,radius: 200,startAngle: CGFloat(0.0), endAngle: CGFloat(3.0 * Double.pi / 2),clockwise: false)
path.addLine(to: CGPoint(x: 200, y: 0))
let obstacle = obstacleByDuplicatingPath(path, clockwise: true)
obstacle.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(obstacle)
let rotateAction = SKAction.rotate(byAngle: CGFloat((3.0 * CGFloat(Double.pi / 2)) - 90), duration: 0.5)
//obstacle.run(SKAction.repeatForever(rotateAction))
obstacle.run(SKAction.repeat(rotateAction, count: 10))
}
func obstacleByDuplicatingPath(_ path: UIBezierPath, clockwise: Bool) -> SKNode {
let container = SKNode()
var rotationFactor = CGFloat(Double.pi / 2)
if !clockwise {
rotationFactor *= -1
}
for i in 0...3 {
let section = SKShapeNode(path: path.cgPath)
section.fillColor = colors[i]
section.strokeColor = colors[i]
section.zRotation = rotationFactor * CGFloat(i);
let origin = CGPoint(x: 0.0, y: 0.0)
switch i {
case 0:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y - 10))
case 1:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y + 10))
case 2:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y + 10))
case 3:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y - 10))
default:
print("bolbol")
}
container.addChild(section)
}
return container
}
}
edit:
I was thinking about it and I tried to do it via SKAction, I set another action but this time I set their duration to a long one. first it run a action of duration 0.5, then of 2 and at end of 4. I looks pretty good but still not smooth as I want it to be.
here is my code:
let rotateAction = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 0.5)
let rotateAction2 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 2)
let rotateAction3 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 4)
let wait = SKAction.wait(forDuration: 5)
let g1 = SKAction.repeat(rotateAction, count: 10)
let group = SKAction.group([wait, g1, rotateAction2, rotateAction3])
what do you think? there is any way to do it better??
edit 2:
Continued to #Ali Beadle answer, I tried to do it via physics body, the problem now is the when I drag finger on the screen the SKShapeNode (shape) in continue to rotate and never stops. can you detect what is wrong?
class GameScene: SKScene {
var start: CGPoint?
var end:CGPoint?
var startTime: TimeInterval?
let shape = SKShapeNode.init(rectOf: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
shape.fillColor = SKColor.red
shape.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
shape.physicsBody = SKPhysicsBody.init(rectangleOf: CGSize(width: 50, height: 50))
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.isDynamic = true
addChild(shape)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
self.startTime = touch.timestamp
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
var dx = ((self.end?.x)! - (self.start?.x)!)
var dy = ((self.end?.y)! - (self.start?.y)!)
let magnitude:CGFloat = sqrt(dx*dx+dy*dy)
if(magnitude >= 25){
let dt:CGFloat = CGFloat(touch.timestamp - self.startTime!)
if dt > 0.1 {
let speed = magnitude / dt
dx = dx / magnitude
dy = dy / magnitude
print("dx: \(dx), dy: \(dy), speed: \(speed) ")
}
}
let touchPosition = touch.location(in: self)
if touchPosition.x < (self.frame.width / 2) {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(-180)
} else {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(180)
}
}}
I have created an open source prize spinning wheel in Spritekit that uses physics for realistic movement and flapper control. It also allows the user to drag the wheel to spin or generates a random spin by pushing the center of the wheel.
https://github.com/hsilived/SpinWheel
You can add realistic movement like this by using the built-in Physics simulation of SpriteKit. This will allow you to give your wheel a mass and friction and then use forces to rotate it. It will then slow down realistically.
In outline see Simulating Physics in the Apple Documentation:
To use physics in your game, you need to:
Attach physics bodies to nodes in the node tree and configure their physical properties. See SKPhysicsBody.
Define global characteristics of the scene’s physics simulation, such as gravity. See SKPhysicsWorld.
Where necessary to support your gameplay, set the velocity of physics bodies in the scene or apply forces or impulses to them. ...
The most appropriate method for your wheel is probably to make the wheel pinned to the scene and then rotate it with applyAngularImpulse.

Move a sprite randomly using Swift 3

I was trying to make a game where the dragon moves around randomly and the hero has to avoid it. I can make the dragon appear at a random location and move once or twice but to make it continuously move from point to point and then move some more has given me trouble. I think it might be because I'm not waiting for the action to complete before generating all of the random numbers. I tried the following code including labels to prove to myself that the random numbers are generating, at least up to 20...
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = GreenDragonSprite.newInstance()
private var lastUpdateTime : TimeInterval = 0
private var i = 0
override func sceneDidLoad() {
self.lastUpdateTime = 0
let xOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonOrigin = CGPoint(x: xOrigin, y: yOrigin)
greenDragonNode.position = dragonOrigin
greenDragonNode.setScale(0.1)
addChild(greenDragonNode)
}
override func didMove(to view: SKView) {
for i in 1...20 {
let xDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination = CGPoint(x: xDestination, y: yDestination)
let xDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination2 = CGPoint(x: xDestination2, y: yDestination2)
let dragonNodeTravel = SKAction.move(to:dragonDestination, duration: 3.0)
let dragonNodeReturn = SKAction.move(to: dragonDestination2, duration: 3.0)
greenDragonNode.run(SKAction.sequence([dragonNodeTravel, dragonNodeReturn]))
// i += 1
let label = SKLabelNode(text: "\(i)")
label.position = dragonDestination2
addChild(label)
}
}
}
That is happening because only the last action from the for loop is executed. You have to make a queue of actions. So you should append them to an array, and run them in a sequence, like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
func getRandomPoint(withinRect rect:CGRect)->CGPoint{
let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.width)
let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.height)
return CGPoint(x: x, y: y)
}
override func didMove(to view: SKView) {
greenDragonNode.position = self.getRandomPoint(withinRect: frame)
addChild(greenDragonNode)
var actions:[SKAction] = []
for i in 1...20 {
let destination = getRandomPoint(withinRect: frame)
let move = SKAction.move(to:destination, duration: 1.0)
actions.append(move)
let label = SKLabelNode(text: "\(i)")
label.position = destination
addChild(label)
}
let sequence = SKAction.sequence(actions)
greenDragonNode.run(sequence)
}
}

Up and down movement in sprite kit and swift?

Right now, I am creating a new game that will involve a ball that moves up and down the screen by itself without touching a button or the screen. I have been looking everywhere for a solution, but I haven't found one. I know how to make the ball move up and down in objective-c, but not in swift or spritekit. I don't know if the following code will help or not. Any solution is accepted.
class GamePlayScene: SKScene, SKPhysicsContactDelegate {
var ball = SKSpriteNode(imageNamed: "green ball.png")
override func didMoveToView(view: SKView) {
//setup scene
physicsWorld.gravity = CGVector.zeroVector
self.scene?.backgroundColor = UIColor.blackColor()
self.scene?.size = CGSize(width: 640, height: 1136)
ball.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 0.9)
ball.size = CGSize (width: 80, height: 82)
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
self.addChild(ball)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
if ball.position.y == 600 {
self.ball.runAction(moveBallUp)
} else {
self.ball.runAction(moveBallDown)
}
}
}
I suggest you use repeatActionForever. If you start your ball position from the bottom of the screen this sequence should work. If you want to start the ball from the top then switch the positions of the actions in moveUpAndDown with each other.
func moveSprite() {
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
let moveUpAndDown = SKAction.sequence([moveBallUp, moveBallDown])
let moveUpAndDownForever = SKAction.repeatActionForever(moveUpAndDown)
ball.runAction(moveUpAndDownForever)
}

Stop a ball sprite kit with Swift

I need to know how to make a ball slow down after two or three seconds to apply an impulse
func setupPlayer(){
player = SKSpriteNode(imageNamed: "ball")
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: size.width/2, y: playableStart)
let scale: CGFloat = 0.07
player.xScale = scale
player.yScale = scale
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
worlNode.addChild(player)
}
func movePlayer(){
player.physicsBody?.applyImpulse((CGVectorMake( 50, 50)))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
movePlayer()
}
}
One way to decelerate a physics body is to reduce its velocity over time:
override func update(currentTime: CFTimeInterval) {
// This controls how fast/slow the body decelerates
let slowBy:CGFloat = 0.95
let dx = player.physicsBody!.velocity.dx
let dy = player.physicsBody!.velocity.dy
player.physicsBody?.velocity = CGVectorMake(dx * slowBy, dy * slowBy)
}
Are you looking for something like this?
let wait = SKAction.waitForDuration(2.0)
let slow = SKAction.runBlock({
node.physicsBody?.applyImpulse(CGVectorMake(-node.physicsBody?.velocity.dx, -node.physicsBody?.velocity.dy))
//you will probably want to mess with the vector
})
node.runAction(SKAction.sequence([wait, slow]))