Random Duration in SKAction in Swift - swift

How can I random those two objects’ duration?
func start() {
let ajusdtedDuration = NSTimeInterval (frame.size.width / kDefaultXToMovePerSecond)
let moveLeft = SKAction.moveByX(-frame.size.width/2, y: 0, duration: ajusdtedDuration/2)
let resetPosition = SKAction.moveToX(0, duration: 0)
let moveSequence = SKAction.sequence([moveLeft, resetPosition])
runAction(SKAction.repeatActionForever(moveSequence))
}
func stop() {
removeAllActions()
}
And this is another one. Plus, how can I make both of their duration synced?
func startMoving() {
let moveLeft = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: 1)
runAction(SKAction.repeatActionForever(moveLeft))
}

You can create a random number and use it for your duration.
The function could look like that:
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}
To sync both actions, you don't need to do much. Because if you call actions after each other, they will be started at the same time. Just call the function once and use it for both of your functions:
//Number between 0 and 10
let randomTime = randomBetweenNumbers(0, secondNum: 10)
Then you use it. The best way would be to use it as a parameter in your functions:
func startMoving(randomTime: CGFloat) {
let moveLeft = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: randomTime)
runAction(SKAction.repeatActionForever(moveLeft))
}
As you see I use the randomTime parameter for the duration:
startMoving(randomTime)
The same in the start()-function:
func start(randomTime:CGFloat)

Related

SpriteKit constant speed from left to right and left to right

i have a SKSpriteNode, that will move from left to right and right to left.
This is Speed
var currentPosition: CGFloat = 20
var rightSpeed = TimeInterval()
var leftSpeed = TimeInterval()
DidMove when SKNode start moving
override func didMove(to view: SKView) {
rightSpeed = moveToRightSpeed()
leftSpeed = moveToLeftSpeed()
personageMoving(position: currentPosition)
}
Method to start sequence of moving
func personageMoving(position: CGFloat) {
rightSpeed = moveToRightSpeed()
leftSpeed = moveToLeftSpeed()
let moveToRight = SKAction.move(to: CGPoint(x: UIScreen.main.bounds.maxX - 20,
y: -UIScreen.main.bounds.maxY + currentPosition),duration: rightSpeed)
let moveToLeft = SKAction.move(to: CGPoint(x: -UIScreen.main.bounds.maxX + 20,
y: -UIScreen.main.bounds.maxY + currentPosition), duration: leftSpeed)
let sequence = SKAction.sequence([moveToRight, moveToLeft])
let forever = SKAction.repeatForever(sequence)
personage.run(forever, withKey: "move")
}
This functions helps me to check speed
func moveToRightSpeed() -> TimeInterval {
let deltaX = UIScreen.main.bounds.maxX - personage.position.x
let deltaY = CGFloat(1)
let distance = (deltaX * deltaX + deltaY * deltaY).squareRoot()
let pixelsPerSecond = CGFloat(400)
let timeToTravel = distance/pixelsPerSecond
return TimeInterval(timeToTravel)
}
func moveToLeftSpeed() -> TimeInterval {
let deltaX = -UIScreen.main.bounds.maxX - personage.position.x
let deltaY = CGFloat(1)
let distance = (deltaX * deltaX + deltaY * deltaY).squareRoot()
let pixelsPerSecond = CGFloat(400)
let timeToTravel = distance/pixelsPerSecond
return TimeInterval(timeToTravel)
}
And in this method i ask person to go up, after it starts left to right and right to left again.
func personageGoUp() {
personage.removeAction(forKey: "move")
let moveLocation = SKAction.move(to: CGPoint(x: personage.position.x, y: personage.position.y + 100), duration: 0.2)
personage.run(moveLocation) { [weak self] in
guard let self = self else { return }
self.currentPosition += 100
self.personageMoving(position: self.currentPosition)
}
}
Problem is speed that is always changing, i can not understand how can i fix it

How to reference the node from within a custom SKAction Swift

I can create a custom SKAction as follows:
extension SKAction {
class func move(from: CGPoint, halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
which I then use like this:
//Setup
let node = SKNode()
node.position = CGPoint(x: 50, y: 50)
let destination = CGPoint(x: 100, y: 100)
//Move from node's current position half way towards the destination
let action = SKAction.move(from: node.position, halfWayTo: destination, duration: 1)
node.run(action)
but in keeping with the standard:
SKAction.move(to: destination, duration: 1)
I would rather be able to say:
let action = SKAction.move(halfWayTo: destination, duration: 1)
node.run(action)
but I don't know how to refer to 'node' from inside the custom SKAction so that I can get it's position and calculate the halfway point?
You use self.
extension SKAction {
class func move(halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let from = self.position
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
Then call with (as you said)
node.move(halfWayTo: B, duration: 1)
I'm still not sure whether the SKAction has a reference to the node it is applied to - even when it's running - or even if it does I'm not sure whether there's a way to access it. If anyone knows that it does please let me know.
I've thought of one approach as follows:
Instead of extending SKAction, extend SKNode as follows:
extension SKNode {
func move(halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let from = self.position
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
and then use as follows:
//Setup
let node = SKNode()
node.position = CGPoint(x: 50, y: 50)
let destination = CGPoint(x: 100, y: 100)
//Move from node's current position half way towards the destination
let action2 = node.move(halfWayTo: destination, duration: 1)
node.run(action2)

Cannot invoke 'moveDistance' with an argument list of type '(distance: (CGVector, fadeInWithDuration: Double))'

Running an SKAction method but having some problems with the syntax. I started from the beginning and filled everything in the way that Xcode 8 code completion was telling me to but still no luck.
Cannot invoke 'moveDistance' with an argument list of type '(distance: (CGVector, fadeInWithDuration: Double))'
a.swift
extension SKAction {
class func moveDistance(distance:CGVector, fadeInWithDuration duration:TimeInterval) -> SKAction {
let fadeIn = SKAction.fadeIn(withDuration: duration)
let moveIn = SKAction.move(by: distance, duration: duration)
return SKAction.group([fadeIn, moveIn])
}
}
b.swift
// legacy
// //gameOverLayer?.runAction(SKAction.moveDistance(CGVectorMake(0, 100), fadeInWithDuration: 0.2)))
// Swift 3
gameOverLayer?.run(SKAction.moveDistance(distance: (CGVector(dx: 0, dy: 100), fadeInWithDuration: 0.2)))
After hrs I realized I added 1 too many parenthesis ...yes noob.
You can refactor your extension method to make it easier for you when calling it as follow:
extension SKAction {
class func move(dx: CGFloat, dy: CGFloat, fadeIn duration: TimeInterval) -> SKAction {
return SKAction.group([ SKAction.fadeIn(withDuration: duration),
SKAction.move(by: CGVector(dx: dx, dy: dy), duration: duration) ])
}
}
Usage:
SKAction.move(dx: 0, dy: 100, fadeIn: 0.2)

How do I spawn multiple nodes without them overlapping?

I have nodes spawning every 0.2-5.0 seconds on my screen like so:
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(blackDots),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func blackDots() {
let dot = SKSpriteNode(imageNamed: "first#2x")
dot.size = CGSizeMake(75, 75)
dot.name = "dotted"
dot.position = CGPointMake(500 * random(min: 0, max: 1), 500 * random(min: 0, max: 1))
addChild(dot)
}
However, when they are spawned, some intersect and lay on top of one another? Is there a way to prevent this? Thanks in advance.
Here's how to check if a node exists at a particular position.
You might want to throw the check into a loop so that if the position is taken, it will retry with a newly generated point. Otherwise you'll get some dots that just won't show. Just depends on what you're doing with them.
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(blackDots),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func blackDots() {
let dot = SKSpriteNode(imageNamed: "first#2x")
dot.size = CGSizeMake(75, 75)
dot.name = "dotted"
let position = CGPointMake(500 * random(min: 0, max: 1), 500 * random(min: 0, max: 1))
if positionIsEmpty(position) {
dot.position = position
addChild(dot)
}
}
func positionIsEmpty(point: CGPoint) -> Bool {
self.enumerateChildNodesWithName("dotted", usingBlock: {
node, stop in
let dot = node as SKSpriteNode
if (CGRectContainsPoint(dot.frame, point)) {
return false
}
})
return true
}
here is this in swift 3.1 please note that this did take me several hours to recreate successfully also a for-loop or TimerInterval won't run this properly it has to be a sequence of actions
like this
let wait = SKAction.wait(forDuration: 1)
let spawn = SKAction.run {
//be sure to call your spawn function here
example()
}
let sequence = SKAction.sequence([wait, spawn])
self.run(SKAction.repeatForever(sequence))
if this is done right it should work like I have it
func example() {
//the person node is equal to an SKSpriteNode subclass
person = people()
func random() -> CGFloat {
//this is the random spawn generator increasing or decreasing these two numbers will change how far or how close they spawn together
return CGFloat(Float(arc4random_uniform(UInt32(500 - 343))))
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnPeople() {
//note that this is set to spawn them on the x-axis but can easily be changed for the y-axis
let position = CGPoint(x: 2 * random(min: 0, max: 1),y: -290)
if positionIsEmpty(point: position) {
//set the position of your node and add it to the scene here any actions you want to add to the node will go here
person.position = position
self.addChild(person)
//example
person.run(parallax1)
}
}
func positionIsEmpty(point: CGPoint) -> Bool {
if (person.frame.contains(point)) {
print("failed")
return false
}
print("success")
return true
}
//make sure to call the spawn(name)() function down here or the code will not run
spawnPeople()
}
also I have all of this code in my didMoveToView function

How can I spawn a random (CLASS SPRITENODE) enemy into my gamescene

I am new is swift programming. I want to spawn an enemies into a random positions using a Class node. I tried to search a code for a random spawning of an enemies but it seems it is irrelevant for my code.
Here is the code I searched for the random spawning.
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed:"spacemonkey_fly02")
override func didMoveToView(view: SKView) {
player.position = CGPoint(x:frame.size.width * 0.1, y: frame.size.height * 0.5)
addChild(player)
backgroundColor = SKColor.blackColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(spawnEnemy),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnEnemy() {
let enemy = SKSpriteNode(imageNamed: "boss_ship")
enemy.name = "enemy"
enemy.position = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
addChild(enemy)
}
}
Here is the Class I made that I want to spawn randomly in my gamescene
import SpriteKit
class Meteor: SKSpriteNode, GameSprite {
var textureAtlas:SKTextureAtlas = SKTextureAtlas(named:"meteor.atlas")
var meteorAnimation = SKAction()
func spawn(parentNode: SKNode, position: CGPoint, size: CGSize = CGSize(width: 30, height: 30)) {
parentNode.addChild(self)
meteorRotation()
self.size = size
self.position = position
self.texture = textureAtlas.textureNamed("meteor-1.png")
self.physicsBody = SKPhysicsBody(texture: textureAtlas.textureNamed("meteor-1.png"), size: size)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = true
self.runAction(meteorAnimation)
}
func meteorRotation() {
let meteorCycle = SKAction.rotateByAngle(4, duration: 2);
meteorAnimation = SKAction.repeatActionForever(meteorCycle)
}
func onTap() {
//self.physicsBody?.affectedByGravity = false
self.physicsBody?.dynamic = false
self.physicsBody?.categoryBitMask = 0
let crashAnimation = SKAction.group([
SKAction.fadeAlphaTo(0, duration: 0.2),
SKAction.scaleTo(1.5, duration: 0.2),
SKAction.moveBy(CGVector(dx: 0, dy: 25), duration: 0.2)
])
let resetAfterCrashed = SKAction.runBlock {
self.position.y = 5000
self.alpha = 1
self.xScale = 1
self.yScale = 1
}
let crashSequence = SKAction.sequence([
crashAnimation,
resetAfterCrashed
])
self.runAction(crashSequence)
}
}
Is it possible to use the code I searched for a class node?
I tried to modify the random spawning code you found to use your meteor class. I took out any lines that didn't involve the meteors, so you'll need to add your world, earth, and field code as you sent me. Give this a shot and let me know if it works out:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(spawnEnemy),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnEnemy() {
let newMeteor = Meteor()
let meteorPosition = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
newMeteor.spawn(world, meteorPosition)
}
}